You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1016 lines
25 KiB

var debug = require("debug");
var debugLog = debug("btcexp:core");
var LRU = require("lru-cache");
var fs = require('fs');
var utils = require("../utils.js");
var config = require("../config.js");
var coins = require("../coins.js");
var redisCache = require("../redisCache.js");
var Decimal = require("decimal.js");
var md5 = require("md5");
// choose one of the below: RPC to a node, or mock data while testing
var rpcApi = require("./rpcApi.js");
//var rpcApi = require("./mockApi.js");
// this value should be incremented whenever data format changes, to avoid
// pulling old-format data from a persistent cache
var cacheKeyVersion = "v1";
const ONE_SEC = 1000;
const ONE_MIN = 60 * ONE_SEC;
const ONE_HR = 60 * ONE_MIN;
const ONE_DAY = 24 * ONE_HR;
const ONE_YR = 265 * ONE_DAY;
function createMemoryLruCache(cacheObj, onCacheEvent) {
return {
get:function(key) {
return new Promise(function(resolve, reject) {
onCacheEvent("memory", "try", key);
var val = cacheObj.get(key);
if (val != null) {
onCacheEvent("memory", "hit", key);
} else {
onCacheEvent("memory", "miss", key);
}
resolve(cacheObj.get(key));
});
},
set:function(key, obj, maxAge) { cacheObj.set(key, obj, maxAge); }
}
}
function tryCache(cacheKey, cacheObjs, index, resolve, reject) {
if (index == cacheObjs.length) {
resolve(null);
return;
}
cacheObjs[index].get(cacheKey).then(function(result) {
if (result != null) {
resolve(result);
} else {
tryCache(cacheKey, cacheObjs, index + 1, resolve, reject);
}
});
}
function createTieredCache(cacheObjs) {
return {
get:function(key) {
return new Promise(function(resolve, reject) {
tryCache(key, cacheObjs, 0, resolve, reject);
});
},
set:function(key, obj, maxAge) {
for (var i = 0; i < cacheObjs.length; i++) {
cacheObjs[i].set(key, obj, maxAge);
}
}
}
}
var miscCaches = [];
var blockCaches = [];
var txCaches = [];
if (!config.noInmemoryRpcCache) {
global.cacheStats.memory = {
try: 0,
hit: 0,
miss: 0
};
var onMemoryCacheEvent = function(cacheType, eventType, cacheKey) {
global.cacheStats.memory[eventType]++;
//debugLog(`cache.${cacheType}.${eventType}: ${cacheKey}`);
}
miscCaches.push(createMemoryLruCache(new LRU(2000), onMemoryCacheEvent));
blockCaches.push(createMemoryLruCache(new LRU(2000), onMemoryCacheEvent));
txCaches.push(createMemoryLruCache(new LRU(10000), onMemoryCacheEvent));
}
if (redisCache.active) {
global.cacheStats.redis = {
try: 0,
hit: 0,
miss: 0,
error: 0
};
var onRedisCacheEvent = function(cacheType, eventType, cacheKey) {
global.cacheStats.redis[eventType]++;
//debugLog(`cache.${cacheType}.${eventType}: ${cacheKey}`);
}
// md5 of the active RPC credentials serves as part of the key; this enables
// multiple instances of btc-rpc-explorer (eg mainnet + testnet) to share
// a single redis instance peacefully
var rpcHostPort = `${config.credentials.rpc.host}:${config.credentials.rpc.port}`;
var rpcCredKeyComponent = md5(JSON.stringify(config.credentials.rpc)).substring(0, 8);
var redisCacheObj = redisCache.createCache(`${cacheKeyVersion}-${rpcCredKeyComponent}`, onRedisCacheEvent);
miscCaches.push(redisCacheObj);
blockCaches.push(redisCacheObj);
txCaches.push(redisCacheObj);
}
var miscCache = createTieredCache(miscCaches);
var blockCache = createTieredCache(blockCaches);
var txCache = createTieredCache(txCaches);
function getGenesisBlockHash() {
return coins[config.coin].genesisBlockHashesByNetwork[global.activeBlockchain];
}
function getGenesisCoinbaseTransactionId() {
return coins[config.coin].genesisCoinbaseTransactionIdsByNetwork[global.activeBlockchain];
}
function tryCacheThenRpcApi(cache, cacheKey, cacheMaxAge, rpcApiFunction, cacheConditionFunction) {
//debugLog("tryCache: " + versionedCacheKey + ", " + cacheMaxAge);
if (cacheConditionFunction == null) {
cacheConditionFunction = function(obj) {
return true;
};
}
return new Promise(function(resolve, reject) {
var cacheResult = null;
var finallyFunc = function() {
if (cacheResult != null) {
resolve(cacheResult);
} else {
rpcApiFunction().then(function(rpcResult) {
if (rpcResult != null && cacheConditionFunction(rpcResult)) {
cache.set(cacheKey, rpcResult, cacheMaxAge);
}
resolve(rpcResult);
}).catch(function(err) {
reject(err);
});
}
};
cache.get(cacheKey).then(function(result) {
cacheResult = result;
finallyFunc();
}).catch(function(err) {
utils.logError("nds9fc2eg621tf3", err, {cacheKey:cacheKey});
finallyFunc();
});
});
}
function shouldCacheTransaction(tx) {
if (!tx.confirmations) {
return false;
}
if (tx.confirmations < 1) {
return false;
}
if (tx.vin != null && tx.vin.length > 9) {
return false;
}
return true;
}
function getBlockchainInfo() {
return tryCacheThenRpcApi(miscCache, "getBlockchainInfo", 10 * ONE_SEC, rpcApi.getBlockchainInfo);
}
function getNetworkInfo() {
return tryCacheThenRpcApi(miscCache, "getNetworkInfo", 10 * ONE_SEC, rpcApi.getNetworkInfo);
}
function getNetTotals() {
return tryCacheThenRpcApi(miscCache, "getNetTotals", 10 * ONE_SEC, rpcApi.getNetTotals);
}
function getMempoolInfo() {
return tryCacheThenRpcApi(miscCache, "getMempoolInfo", ONE_SEC, rpcApi.getMempoolInfo);
}
function getMempoolTxids() {
// no caching, that would be dumb
return rpcApi.getMempoolTxids();
}
function getMiningInfo() {
return tryCacheThenRpcApi(miscCache, "getMiningInfo", 30 * ONE_SEC, rpcApi.getMiningInfo);
}
function getUptimeSeconds() {
return tryCacheThenRpcApi(miscCache, "getUptimeSeconds", ONE_SEC, rpcApi.getUptimeSeconds);
}
function getChainTxStats(blockCount) {
return tryCacheThenRpcApi(miscCache, "getChainTxStats-" + blockCount, 20 * ONE_MIN, function() {
return rpcApi.getChainTxStats(blockCount);
});
}
function getNetworkHashrate(blockCount) {
return tryCacheThenRpcApi(miscCache, "getNetworkHashrate-" + blockCount, 20 * ONE_MIN, function() {
return rpcApi.getNetworkHashrate(blockCount);
});
}
function getBlockStats(hash) {
return tryCacheThenRpcApi(miscCache, "getBlockStats-" + hash, ONE_YR, function() {
return rpcApi.getBlockStats(hash);
});
}
function getBlockStatsByHeight(height) {
return tryCacheThenRpcApi(miscCache, "getBlockStatsByHeight-" + height, ONE_YR, function() {
return rpcApi.getBlockStatsByHeight(height);
});
}
function getUtxoSetSummary() {
return tryCacheThenRpcApi(miscCache, "getUtxoSetSummary", 15 * ONE_MIN, rpcApi.getUtxoSetSummary);
}
function getTxCountStats(dataPtCount, blockStart, blockEnd) {
return new Promise(function(resolve, reject) {
var dataPoints = dataPtCount;
getBlockchainInfo().then(function(getblockchaininfo) {
if (typeof blockStart === "string") {
if (["genesis", "first", "zero"].includes(blockStart)) {
blockStart = 0;
}
}
if (typeof blockEnd === "string") {
if (["latest", "tip", "newest"].includes(blockEnd)) {
blockEnd = getblockchaininfo.blocks;
}
}
if (blockStart > blockEnd) {
reject(`Error 37rhw0e7ufdsgf: blockStart (${blockStart}) > blockEnd (${blockEnd})`);
return;
}
if (blockStart < 0) {
blockStart += getblockchaininfo.blocks;
}
if (blockEnd < 0) {
blockEnd += getblockchaininfo.blocks;
}
var chainTxStatsIntervals = [];
for (var i = 0; i < dataPoints; i++) {
chainTxStatsIntervals.push(parseInt(Math.max(10, getblockchaininfo.blocks - blockStart - i * (blockEnd - blockStart) / (dataPoints - 1) - 1)));
}
var promises = [];
for (var i = 0; i < chainTxStatsIntervals.length; i++) {
promises.push(getChainTxStats(chainTxStatsIntervals[i]));
}
Promise.all(promises).then(function(results) {
if (results[0].name == "RpcError" && results[0].code == -8) {
// recently started node - no meaningful data to return
resolve(null);
return;
}
var txStats = {
txCounts: [],
txLabels: [],
txRates: []
};
for (var i = results.length - 1; i >= 0; i--) {
if (results[i].window_tx_count) {
txStats.txCounts.push( {x:(getblockchaininfo.blocks - results[i].window_block_count), y: (results[i].txcount - results[i].window_tx_count)} );
txStats.txRates.push( {x:(getblockchaininfo.blocks - results[i].window_block_count), y: (results[i].txrate)} );
txStats.txLabels.push(i);
}
}
resolve({txCountStats:txStats, getblockchaininfo:getblockchaininfo, totalTxCount:results[0].txcount});
}).catch(function(err) {
reject(err);
});
}).catch(function(err) {
reject(err);
});
});
}
function getSmartFeeEstimates(mode, confTargetBlockCounts) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < confTargetBlockCounts.length; i++) {
promises.push(getSmartFeeEstimate(mode, confTargetBlockCounts[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getSmartFeeEstimate(mode, confTargetBlockCount) {
return tryCacheThenRpcApi(miscCache, "getSmartFeeEstimate-" + mode + "-" + confTargetBlockCount, 5 * ONE_MIN, function() {
return rpcApi.getSmartFeeEstimate(mode, confTargetBlockCount);
});
}
function getPeerSummary() {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getpeerinfo", ONE_SEC, rpcApi.getPeerInfo).then(function(getpeerinfo) {
var result = {};
result.getpeerinfo = getpeerinfo;
var versionSummaryMap = {};
for (var i = 0; i < getpeerinfo.length; i++) {
var x = getpeerinfo[i];
if (versionSummaryMap[x.subver] == null) {
versionSummaryMap[x.subver] = 0;
}
versionSummaryMap[x.subver]++;
}
var versionSummary = [];
for (var prop in versionSummaryMap) {
if (versionSummaryMap.hasOwnProperty(prop)) {
versionSummary.push([prop, versionSummaryMap[prop]]);
}
}
versionSummary.sort(function(a, b) {
if (b[1] > a[1]) {
return 1;
} else if (b[1] < a[1]) {
return -1;
} else {
return a[0].localeCompare(b[0]);
}
});
var servicesSummaryMap = {};
for (var i = 0; i < getpeerinfo.length; i++) {
var x = getpeerinfo[i];
if (servicesSummaryMap[x.services] == null) {
servicesSummaryMap[x.services] = 0;
}
servicesSummaryMap[x.services]++;
}
var servicesSummary = [];
for (var prop in servicesSummaryMap) {
if (servicesSummaryMap.hasOwnProperty(prop)) {
servicesSummary.push([prop, servicesSummaryMap[prop]]);
}
}
servicesSummary.sort(function(a, b) {
if (b[1] > a[1]) {
return 1;
} else if (b[1] < a[1]) {
return -1;
} else {
return a[0].localeCompare(b[0]);
}
});
result.versionSummary = versionSummary;
result.servicesSummary = servicesSummary;
resolve(result);
}).catch(function(err) {
reject(err);
});
});
}
function getMempoolDetails(start, count) {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getMempoolTxids", ONE_SEC, rpcApi.getMempoolTxids).then(function(resultTxids) {
var txids = [];
for (var i = start; (i < resultTxids.length && i < (start + count)); i++) {
txids.push(resultTxids[i]);
}
getRawTransactionsWithInputs(txids, config.site.txMaxInput).then(function(result) {
resolve({ txCount:resultTxids.length, transactions:result.transactions, txInputsByTransaction:result.txInputsByTransaction });
});
}).catch(function(err) {
reject(err);
});
});
}
function getBlockByHeight(blockHeight) {
return tryCacheThenRpcApi(blockCache, "getBlockByHeight-" + blockHeight, ONE_HR, function() {
return rpcApi.getBlockByHeight(blockHeight);
});
}
function getBlocksByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockByHeight(blockHeights[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getBlockHeaderByHeight(blockHeight) {
return tryCacheThenRpcApi(blockCache, "getBlockHeaderByHeight-" + blockHeight, ONE_HR, function() {
return rpcApi.getBlockHeaderByHeight(blockHeight);
});
}
function getBlockHeadersByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockHeaderByHeight(blockHeights[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getBlocksStatsByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockStatsByHeight(blockHeights[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getBlockByHash(blockHash) {
return tryCacheThenRpcApi(blockCache, "getBlockByHash-" + blockHash, ONE_HR, function() {
return rpcApi.getBlockByHash(blockHash);
});
}
function getBlocksByHash(blockHashes) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHashes.length; i++) {
promises.push(getBlockByHash(blockHashes[i]));
}
Promise.all(promises).then(function(results) {
var result = {};
results.forEach(function(item) {
result[item.hash] = item;
});
resolve(result);
}).catch(function(err) {
reject(err);
});
});
}
function getRawTransaction(txid) {
var rpcApiFunction = function() {
return rpcApi.getRawTransaction(txid);
};
return tryCacheThenRpcApi(txCache, "getRawTransaction-" + txid, ONE_HR, rpcApiFunction, shouldCacheTransaction);
}
/*
This function pulls raw tx data and then summarizes the outputs. It's used in memory-constrained situations.
*/
function getSummarizedTransactionOutput(txid, voutIndex) {
var rpcApiFunction = function() {
return new Promise(function(resolve, reject) {
rpcApi.getRawTransaction(txid).then(function(rawTx) {
var vout = rawTx.vout[voutIndex];
if (vout.scriptPubKey) {
if (vout.scriptPubKey.asm) {
delete vout.scriptPubKey.asm;
}
if (vout.scriptPubKey.hex) {
delete vout.scriptPubKey.hex;
}
}
vout.txid = txid;
vout.utxoTime = rawTx.time;
if (rawTx.vin.length == 1 && rawTx.vin[0].coinbase) {
vout.coinbaseSpend = true;
}
resolve(vout);
}).catch(function(err) {
reject(err);
});
});
};
return tryCacheThenRpcApi(txCache, `txoSummary-${txid}-${voutIndex}`, ONE_HR, rpcApiFunction, function() { return true; });
}
function getTxUtxos(tx) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < tx.vout.length; i++) {
promises.push(getUtxo(tx.txid, i));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getUtxo(txid, outputIndex) {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "utxo-" + txid + "-" + outputIndex, ONE_HR, function() {
return rpcApi.getUtxo(txid, outputIndex);
}).then(function(result) {
// to avoid cache misses, rpcApi.getUtxo returns "0" instead of null
if (result == "0") {
resolve(null);
return;
}
resolve(result);
}).catch(function(err) {
reject(err);
});
});
}
function getMempoolTxDetails(txid, includeAncDec) {
return tryCacheThenRpcApi(miscCache, "mempoolTxDetails-" + txid + "-" + includeAncDec, ONE_HR, function() {
return rpcApi.getMempoolTxDetails(txid, includeAncDec);
});
}
function getAddress(address) {
return tryCacheThenRpcApi(miscCache, "getAddress-" + address, ONE_HR, function() {
return rpcApi.getAddress(address);
});
}
function getRawTransactions(txids) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < txids.length; i++) {
promises.push(getRawTransaction(txids[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function buildBlockAnalysisData(blockHeight, txids, txIndex, results, callback) {
if (txIndex >= txids.length) {
callback();
return;
}
var txid = txids[txIndex];
getRawTransactionsWithInputs([txid]).then(function(txData) {
results.push(summarizeBlockAnalysisData(blockHeight, txData.transactions[0], txData.txInputsByTransaction[txid]));
buildBlockAnalysisData(blockHeight, txids, txIndex + 1, results, callback);
});
}
function summarizeBlockAnalysisData(blockHeight, tx, inputs) {
var txSummary = {};
txSummary.txid = tx.txid;
txSummary.version = tx.version;
txSummary.size = tx.size;
if (tx.vsize) {
txSummary.vsize = tx.vsize;
}
if (tx.weight) {
txSummary.weight = tx.weight;
}
if (tx.vin[0].coinbase) {
txSummary.coinbase = true;
}
txSummary.vin = [];
txSummary.totalInput = new Decimal(0);
if (txSummary.coinbase) {
var subsidy = global.coinConfig.blockRewardFunction(blockHeight, global.activeBlockchain);
txSummary.totalInput = txSummary.totalInput.plus(new Decimal(subsidy));
txSummary.vin.push({
coinbase: true,
value: subsidy
});
} else {
for (var i = 0; i < tx.vin.length; i++) {
var vin = tx.vin[i];
var inputVout = inputs[i];
txSummary.totalInput = txSummary.totalInput.plus(new Decimal(inputVout.value));
txSummary.vin.push({
txid: tx.vin[i].txid,
vout: tx.vin[i].vout,
sequence: tx.vin[i].sequence,
value: inputVout.value,
type: inputVout.scriptPubKey.type,
reqSigs: inputVout.scriptPubKey.reqSigs,
addressCount: (inputVout.scriptPubKey.addresses ? inputVout.scriptPubKey.addresses.length : 0)
});
}
}
txSummary.vout = [];
txSummary.totalOutput = new Decimal(0);
for (var i = 0; i < tx.vout.length; i++) {
txSummary.totalOutput = txSummary.totalOutput.plus(new Decimal(tx.vout[i].value));
txSummary.vout.push({
value: tx.vout[i].value,
type: tx.vout[i].scriptPubKey.type,
reqSigs: tx.vout[i].scriptPubKey.reqSigs,
addressCount: tx.vout[i].scriptPubKey.addresses ? tx.vout[i].scriptPubKey.addresses.length : 0
});
}
if (txSummary.coinbase) {
txSummary.totalFee = new Decimal(0);
} else {
txSummary.totalFee = txSummary.totalInput.minus(txSummary.totalOutput);
}
return txSummary;
}
function getRawTransactionsWithInputs(txids, maxInputs=-1) {
return new Promise(function(resolve, reject) {
getRawTransactions(txids).then(function(transactions) {
var maxInputsTracked = config.site.txMaxInput;
if (maxInputs <= 0) {
maxInputsTracked = 1000000;
} else if (maxInputs > 0) {
maxInputsTracked = maxInputs;
}
var vinIds = [];
for (var i = 0; i < transactions.length; i++) {
var transaction = transactions[i];
if (transaction && transaction.vin) {
for (var j = 0; j < Math.min(maxInputsTracked, transaction.vin.length); j++) {
if (transaction.vin[j].txid) {
vinIds.push({txid:transaction.vin[j].txid, voutIndex:transaction.vin[j].vout});
}
}
}
}
var promises = [];
for (var i = 0; i < vinIds.length; i++) {
var vinId = vinIds[i];
promises.push(getSummarizedTransactionOutput(vinId.txid, vinId.voutIndex));
}
Promise.all(promises).then(function(promiseResults) {
var summarizedTxOutputs = {};
for (var i = 0; i < promiseResults.length; i++) {
var summarizedTxOutput = promiseResults[i];
summarizedTxOutputs[`${summarizedTxOutput.txid}:${summarizedTxOutput.n}`] = summarizedTxOutput;
}
var txInputsByTransaction = {};
transactions.forEach(function(tx) {
txInputsByTransaction[tx.txid] = {};
if (tx && tx.vin) {
for (var i = 0; i < Math.min(maxInputsTracked, tx.vin.length); i++) {
var summarizedTxOutput = summarizedTxOutputs[`${tx.vin[i].txid}:${tx.vin[i].vout}`];
if (summarizedTxOutput) {
txInputsByTransaction[tx.txid][i] = summarizedTxOutput;
}
}
}
});
resolve({ transactions:transactions, txInputsByTransaction:txInputsByTransaction });
});
}).catch(function(err) {
reject(err);
});
});
}
function getBlockByHashWithTransactions(blockHash, txLimit, txOffset) {
return new Promise(function(resolve, reject) {
getBlockByHash(blockHash).then(function(block) {
var txids = [];
if (txOffset > 0) {
txids.push(block.tx[0]);
}
for (var i = txOffset; i < Math.min(txOffset + txLimit, block.tx.length); i++) {
txids.push(block.tx[i]);
}
getRawTransactionsWithInputs(txids, config.site.txMaxInput).then(function(txsResult) {
if (txsResult.transactions && txsResult.transactions.length > 0) {
block.coinbaseTx = txsResult.transactions[0];
block.totalFees = utils.getBlockTotalFeesFromCoinbaseTxAndBlockHeight(block.coinbaseTx, block.height);
block.miner = utils.getMinerFromCoinbaseTx(block.coinbaseTx);
}
// if we're on page 2, we don't really want the coinbase tx in the tx list anymore
if (txOffset > 0) {
txsResult.transactions.shift();
}
resolve({ getblock:block, transactions:txsResult.transactions, txInputsByTransaction:txsResult.txInputsByTransaction });
});
}).catch(function(err) {
reject(err);
});
});
}
function getHelp() {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getHelp", ONE_DAY, rpcApi.getHelp).then(function(helpContent) {
var lines = helpContent.split("\n");
var sections = [];
lines.forEach(function(line) {
if (line.startsWith("==")) {
var sectionName = line.substring(2);
sectionName = sectionName.substring(0, sectionName.length - 2).trim();
sections.push({name:sectionName, methods:[]});
} else if (line.trim().length > 0) {
var methodName = line.trim();
if (methodName.includes(" ")) {
methodName = methodName.substring(0, methodName.indexOf(" "));
}
sections[sections.length - 1].methods.push({name:methodName, content:line.trim()});
}
});
resolve(sections);
}).catch(function(err) {
reject(err);
});
});
}
function getRpcMethodHelp(methodName) {
var rpcApiFunction = function() {
return rpcApi.getRpcMethodHelp(methodName);
};
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getHelp-" + methodName, ONE_DAY, rpcApiFunction).then(function(helpContent) {
var output = {};
output.string = helpContent;
var str = helpContent;
var lines = str.split("\n");
var argumentLines = [];
var catchArgs = false;
lines.forEach(function(line) {
if (line.trim().length == 0) {
catchArgs = false;
}
if (catchArgs) {
argumentLines.push(line);
}
if (line.trim() == "Arguments:" || line.trim() == "Arguments") {
catchArgs = true;
}
});
var args = [];
var argX = null;
// looking for line starting with "N. " where N is an integer (1-2 digits)
argumentLines.forEach(function(line) {
var regex = /^([0-9]+)\.\s*"?(\w+)"?\s*\(([^,)]*),?\s*([^,)]*),?\s*([^,)]*),?\s*([^,)]*)?\s*\)\s*(.+)?$/;
var match = regex.exec(line);
if (match) {
argX = {};
argX.name = match[2];
argX.detailsLines = [];
argX.properties = [];
if (match[3]) {
argX.properties.push(match[3]);
}
if (match[4]) {
argX.properties.push(match[4]);
}
if (match[5]) {
argX.properties.push(match[5]);
}
if (match[6]) {
argX.properties.push(match[6]);
}
if (match[7]) {
argX.description = match[7];
}
args.push(argX);
}
if (!match && argX) {
argX.detailsLines.push(line);
}
});
output.args = args;
resolve(output);
}).catch(function(err) {
reject(err);
});
});
}
function logCacheSizes() {
var itemCounts = [ miscCache.itemCount, blockCache.itemCount, txCache.itemCount ];
var stream = fs.createWriteStream("memoryUsage.csv", {flags:'a'});
stream.write("itemCounts: " + JSON.stringify(itemCounts) + "\n");
stream.end();
}
module.exports = {
getGenesisBlockHash: getGenesisBlockHash,
getGenesisCoinbaseTransactionId: getGenesisCoinbaseTransactionId,
getBlockchainInfo: getBlockchainInfo,
getNetworkInfo: getNetworkInfo,
getNetTotals: getNetTotals,
getMempoolInfo: getMempoolInfo,
getMempoolTxids: getMempoolTxids,
getMiningInfo: getMiningInfo,
getBlockByHeight: getBlockByHeight,
getBlocksByHeight: getBlocksByHeight,
getBlockByHash: getBlockByHash,
getBlocksByHash: getBlocksByHash,
getBlockByHashWithTransactions: getBlockByHashWithTransactions,
getRawTransaction: getRawTransaction,
getRawTransactions: getRawTransactions,
getRawTransactionsWithInputs: getRawTransactionsWithInputs,
getTxUtxos: getTxUtxos,
getMempoolTxDetails: getMempoolTxDetails,
getUptimeSeconds: getUptimeSeconds,
getHelp: getHelp,
getRpcMethodHelp: getRpcMethodHelp,
getAddress: getAddress,
logCacheSizes: logCacheSizes,
getPeerSummary: getPeerSummary,
getChainTxStats: getChainTxStats,
getMempoolDetails: getMempoolDetails,
getTxCountStats: getTxCountStats,
getSmartFeeEstimates: getSmartFeeEstimates,
getSmartFeeEstimate: getSmartFeeEstimate,
getUtxoSetSummary: getUtxoSetSummary,
getNetworkHashrate: getNetworkHashrate,
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
getBlocksStatsByHeight: getBlocksStatsByHeight,
buildBlockAnalysisData: buildBlockAnalysisData,
getBlockHeaderByHeight: getBlockHeaderByHeight,
getBlockHeadersByHeight: getBlockHeadersByHeight
};