/* * This file is part of the Flowee project * Copyright (C) 2019-2021 Tom Zander * Copyright (C) 2020 James Zuccon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ const Message = require('./message'); /** * Blockchain information service. * @param {Flowee} instance Instance of Flowee */ class BlockchainService { constructor(network) { this.network = network; } /** * Get Blockchain Information * @example * let blockChainInfo = await flowee.getBlockChainInfo(); */ async getBlockChainInfo() { let reply = await this.network.sendMessage(new Message(1, 0)); return { difficulty: reply[64], medianTime: reply[65], chainWork: reply["66str"], chain: reply[67], blocks: reply[68], headers: reply[69], bestBlockHash: reply[70], bestBlockId: reply["70str"], }; } /** * Get the best block hash available * @returns The an object with the block hash as string and buffer. * @example * let bestBlockHash = await flowee.getBestBlockHash(); */ async getBestBlockHash() { let reply = await this.network.sendMessage(new Message(1, 2)); return { blockId: reply["1str"], blockHash: reply[1], } } /** * Get Block height * @returns {number} The block height * @example * let blockCount = await flowee.getBlockCount(); */ async getBlockCount() { let reply = await this.network.sendMessage(new Message(1, 10)); return reply[7]; // Block Height (is equal to count since genesis is height=0) } /** * Get Block * @param param (Number or Hash) The block height * @returns {number} Object containing details about block * @example * let blockHeader = await flowee.getBlock(500000); */ async getBlock(param, filters) { let message = new Message(1, 4); message.body[44] = true; // Include_OffsetInBlock message.body[45] = false; // Include_FullTxData if (typeof param === 'number') { message.body[7] = param; } else if (param * 1 == parseInt(param)) { // is really a number message.body[7] = parseInt(param); } else if (typeof param === 'string') { message.body[5] = Buffer.from(param, 'hex').reverse(); if (message.body[5].length != 32) throw "Invalid argument, not blockId"; } else if (typeof param == 'undefined') { throw "Missing argument"; } else { message.body[5] = param; } applyFilters(message, filers); let reply = await this.network.sendMessage(message); let answer = { blockId: reply["5str"], blockHash: reply[5], blockHeight: reply[7], transactions: [] } let tx = null; for (let prop of reply.body) { if (prop.key == 0) { if (tx != null) answer.transactions.push(tx); tx = null; } else if (prop.key == 8) { tx = {}; tx.offset = prop.value; } } return answer; } async getTransaction(blockHeight, offsetInBlock, filters) { if (blockHeight * 1 == parseInt(blockHeight)) { // is really a number blockHeight = blockHeight * 1; } if (typeof blockHeight === 'string') { // assume this is a txid if (typeof offsetInBlock === 'object') { // move arg filters = offsetInBlock; } var search = await this.network.search({ jobs: [{ value: blockHeight, type: this.network.Job.FetchTx, txFilter: filters }]}); return search.transactions; } if (offsetInBlock * 1 == parseInt(offsetInBlock)) { // is really a number offsetInBlock = offsetInBlock * 1; } if (typeof offsetInBlock !== 'number') throw "OffsetInBlock should be a number"; if (typeof filters[0] !== 'number') { // set some defaults filters[0] = this.network.IncludeOffsetInBlock; filters[1] = this.network.IncludeOutputs; filters[2] = this.network.IncludeInputs; } let message = new Message(1, 12); message.body[7] = blockHeight; message.body[8] = offsetInBlock; applyFilters(message, filers); let reply = await this.network.sendMessage(message); let outputs = []; let inputs = []; let currentInput = -1; let currentOutput = 0; for (let row of reply.body) { switch (row.key) { case '6': // amount outputs[currentOutput].value = row.value; break; case '2': // p2pkh address outputs[currentOutput].p2pkhAddress = row.value; break; case '9': // BitcoinScriptHashed outputs[currentOutput].scriptHashed = row.value; break; case '20': // Tx_IN_TXID inputs[++currentInput] = { prevTxid: row.value }; break; case '21': // Tx_IN_OutIndex inputs[currentInput].outIndex = row.value; break; case '22': // Tx_InputScript inputs[currentInput].script = row.value; break; case '23': // Tx_OutputScript outputs[currentOutput].script = row.value; break; case '24': // Tx_Out_Index currentOutput = row.value; outputs[currentOutput] = {} break; } } return { raw: reply[1], txid: reply[2], offsetInBlock: reply[8], inputs: inputs, outputs: outputs, }; } /// For block and transaction fetching messages, apply the filters. applyFilters(message, filters) { for (let filter of filters) { switch (filter) { case this.network.IncludeTxid: message.body[43] = true; break; case this.network.IncludeOffsetInBlock: message.body[44] = true; break; case this.network.IncludeFullTxData: message.body[45] = true; break; case this.network.IncludeInputs: message.body[46] = true; break; case this.network.IncludeOutputAmounts: message.body[47] = true; break; case this.network.IncludeOutputScripts: message.body[48] = true; break; case this.network.IncludeOutputs: message.body[49] = true; break; case this.network.IncludeOutputAddresses: message.body[50] = true; break; case this.network.IncludeOutputScriptHash: message.body[51] = true; break; } } } async getRawTransaction(blockHeight, offsetInBlock) { if (blockHeight * 1 == parseInt(blockHeight)) { // is really a number blockHeight = blockHeight * 1; } if (typeof blockHeight === 'string') { // assume this is a txid var search = await this.network.search({ jobs: [{ value: blockHeight, type: this.network.Job.FetchTx, txFilter: [ this.network.IncludeFullTxData ] }]}); return search.transactions[0].fullTxData; } if (typeof blockHeight === 'number' && typeof offsetInBlock === 'number') { let message = new Message(1, 12); message.body[7] = blockHeight; message.body[8] = offsetInBlock; message.body[45] = true; // Include_FullTxData let reply = await this.network.sendMessage(message); return reply["1"]; } else throw "Missing arguments"; } async getBlockHeader(blockHeight) { if (blockHeight * 1 == parseInt(blockHeight)) { // is really a number blockHeight = blockHeight * 1; } let message = new Message(1, 8); if (typeof blockHeight === 'number') { message.body[7] = blockHeight; } else if (typeof blockHeight === 'string') { message.body[5] = Buffer.from(param, 'hex').reverse(); if (message.body[5].length != 32) throw "Invalid argument, not blockId"; } else throw "Missing arguments"; let reply = await this.network.sendMessage(message); return { blockId: reply["5str"], blockHash: reply[5], confirmations: reply[72], blockHeight: reply[7], version: reply[62], merkleRoot: reply[73], merkleRootHex: reply["73str"], time: reply[63], medianTime: reply[65], nonce: reply[74], bits: reply[75], difficulty: reply[64], prevBlockHash: reply["76str"], nextBlockHash: reply["77str"] }; } } module.exports = BlockchainService;