Files
js/lib/service.blockchain.js

294 lines
8.8 KiB
JavaScript

/*
* This file is part of the Flowee project
* Copyright (C) 2019-2021 Tom Zander <tom@flowee.org>
* Copyright (C) 2020 James Zuccon <zuccon@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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;