UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

213 lines 10.5 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.blocks = blocks; exports.transactions = transactions; const chai_1 = require("chai"); const config_1 = __importDefault(require("../../src/config")); const logger_1 = __importDefault(require("../../src/logger")); const block_1 = require("../../src/models/block"); const coin_1 = require("../../src/models/coin"); const transaction_1 = require("../../src/models/transaction"); const walletAddress_1 = require("../../src/models/walletAddress"); const chain_state_1 = require("../../src/providers/chain-state"); const rpc_1 = require("../../src/rpc"); const storage_1 = require("../../src/services/storage"); const SATOSHI = 100000000.0; async function blocks(info, creds) { const rpc = new rpc_1.AsyncRPC(creds.username, creds.password, creds.host, creds.port); const tip = await chain_state_1.ChainStateProvider.getLocalTip({ chain: info.chain, network: info.network }); const heights = new Array(tip.height).fill(false); const times = new Array(tip.height).fill(0); const normalizedTimes = new Array(tip.height).fill(0); // check each block const cursor = block_1.BitcoinBlockStorage.collection.find({ chain: info.chain, network: info.network }); while (await cursor.hasNext()) { const block = await cursor.next(); if (!block) break; if (!block.processed) continue; logger_1.default.info(`verifying block ${block.hash}: ${block.height}`); // Check there's all unique heights (0, chai_1.expect)(block.height, 'block height').to.be.gte(1); (0, chai_1.expect)(block.height, 'block height').to.be.lte(tip.height); (0, chai_1.expect)(heights[block.height - 1], 'height already used').to.be.false; heights[block.height - 1] = true; // Check times are increasing times[block.height - 1] = block.time.getTime(); normalizedTimes[block.height - 1] = block.timeNormalized.getTime(); const truth = await rpc.verbose_block(block.hash); (0, chai_1.expect)(block.height, 'block height').to.equal(truth.height); (0, chai_1.expect)(block.hash, 'block hash').to.equal(truth.hash); (0, chai_1.expect)(block.version, 'block version').to.equal(truth.version); (0, chai_1.expect)(block.merkleRoot, 'block merkle root').to.equal(truth.merkleroot); (0, chai_1.expect)(block.nonce, 'block nonce').to.equal(truth.nonce); (0, chai_1.expect)(block.previousBlockHash, 'block prev hash').to.equal(truth.previousblockhash); (0, chai_1.expect)(block.transactionCount, 'block tx count').to.equal(truth.tx.length); if (info.network !== 'regtest') { (0, chai_1.expect)(block.size, 'block size').to.equal(truth.size); } (0, chai_1.expect)(block.bits.toString(16), 'block bits').to.equal(truth.bits); (0, chai_1.expect)(block.processed, 'block processed').to.equal(true); (0, chai_1.expect)(block.time.getTime(), 'block time').to.equal(truth.time * 1000); if (block.height < tip.height) { (0, chai_1.expect)(block.nextBlockHash, 'block next hash').to.equal(truth.nextblockhash); } // Transaction Specifics { const coinbase = truth.tx[0]; // Check reward const reward = coinbase.vout.reduce((a, b) => a + b.value, 0); (0, chai_1.expect)(block.reward, 'block reward').to.equal(Math.round(reward * SATOSHI)); // Check block only has all `truth`'s transactions const ours = await transaction_1.TransactionStorage.collection .find({ chain: info.chain, network: info.network, txid: { $in: truth.tx.map(tx => tx.txid) } }) .project({ txid: true, coinbase: true, blockHash: true, blockHeight: true, blockTime: true, blockTimeNormalized: true }) .toArray(); // Check coinbase flag const ourCoinbase = ours.filter(tx => tx.coinbase); (0, chai_1.expect)(ourCoinbase.length, 'number of coinbases').to.equal(1); (0, chai_1.expect)(ourCoinbase[0].txid, 'coinbase txid to match truth').to.equal(coinbase.txid); // Check both sets of txs are the same size and contain no duplicates const txidset = new Set(ours.map(tx => tx.txid)); (0, chai_1.expect)(ours.length, 'number of txs').to.equal(truth.tx.length); (0, chai_1.expect)(txidset.size, 'number of unique txs').to.equal(truth.tx.length); for (const our of ours) { // Check every one of our txs is contained in `truth` const tx = truth.tx.find(tx => tx.txid === our.txid); (0, chai_1.expect)(tx, 'tx to be in the block').to.not.be.undefined; // Check our txs' block hash matches the mongo block (0, chai_1.expect)(our.blockHash, 'tx block hash').to.equal(block.hash); (0, chai_1.expect)(our.blockHeight, 'tx block height').to.equal(block.height); const time = our.blockTime && our.blockTime.getTime(); (0, chai_1.expect)(time, 'tx block time').to.equal(block.time.getTime()); const ntime = our.blockTimeNormalized && our.blockTimeNormalized.getTime(); (0, chai_1.expect)(ntime, 'tx block time normalized').to.equal(block.timeNormalized.getTime()); } // Check no other tx points to our block hash const extra = await transaction_1.TransactionStorage.collection.countDocuments({ chain: info.chain, network: info.network, blockHash: block.hash, txid: { $nin: truth.tx.map(tx => tx.txid) } }); (0, chai_1.expect)(extra, 'number of extra transactions').to.equal(0); } } // Check the heights are all unique (0, chai_1.expect)(heights.filter(h => !h).length, 'no duplicate heights').to.equal(0); // Check increasing times const increases = l => !!l.reduce((prev, curr) => (prev < curr ? curr : undefined)); (0, chai_1.expect)(increases(normalizedTimes), 'normalized block times only increase').to.be.true; } async function transactions(info, creds) { const rpc = new rpc_1.AsyncRPC(creds.username, creds.password, creds.host, creds.port); const txcursor = transaction_1.TransactionStorage.collection.find({ chain: info.chain, network: info.network }); while (await txcursor.hasNext()) { const tx = await txcursor.next(); if (!tx) { break; } logger_1.default.info(`verifying tx ${tx.txid}: ${tx.blockHeight}`); const truth = await rpc.transaction(tx.txid, tx.blockHash); if (info.network !== 'regtest') { (0, chai_1.expect)(tx.size, 'tx size').to.equal(truth.size); } (0, chai_1.expect)(tx.locktime, 'tx locktime').to.equal(truth.locktime); { // Minted by this transaction const ours = await coin_1.CoinStorage.collection .find({ network: info.network, chain: info.chain, mintTxid: tx.txid }) .toArray(); (0, chai_1.expect)(ours.length, 'number mint txids').to.equal(truth.vout.length); for (const our of ours) { // coins (0, chai_1.expect)(our.mintHeight, 'tx mint height').to.equal(tx.blockHeight); (0, chai_1.expect)(our.value, 'tx mint value').to.equal(Math.round(truth.vout[our.mintIndex].value * SATOSHI)); // TODO: why? if (our.address && our.address !== 'false') { (0, chai_1.expect)(truth.vout[our.mintIndex].scriptPubKey.addresses, 'tx mint address').to.include(our.address); } (0, chai_1.expect)(our.coinbase).to.equal(tx.coinbase); // wallets (0, chai_1.expect)(tx.wallets).to.include.members(Array.from(our.wallets)); if (our.wallets.length > 0) { const wallets = await walletAddress_1.WalletAddressStorage.collection .find({ wallet: { $in: our.wallets }, address: our.address, chain: info.chain, network: info.network }) .toArray(); (0, chai_1.expect)(wallets.length, 'wallet exists').to.be.greaterThan(0); } } } { // Spent by this transaction const ours = await coin_1.CoinStorage.collection .find({ network: info.network, chain: info.chain, spentTxid: tx.txid }) .toArray(); const nspent = truth.vin.length + (tx.coinbase ? -1 : 0); (0, chai_1.expect)(ours.length, 'number spent txids').to.equal(nspent); for (const our of ours) { (0, chai_1.expect)(our.spentHeight, 'tx spent height').to.equal(tx.blockHeight); (0, chai_1.expect)(tx.wallets).to.include.members(Array.from(our.wallets)); } } } } if (require.main === module) (async () => { const info = { chain: process.env.CHAIN || 'BTC', network: process.env.NETWORK || 'testnet' }; const creds = config_1.default.chains[info.chain][info.network].rpc; await storage_1.Storage.start({}); logger_1.default.info('verifying blocks'); await blocks(info, creds); logger_1.default.info('verifying transactions'); await transactions(info, creds); process.exit(); })().catch(err => { logger_1.default.error('%o', err); process.exit(1); }); //# sourceMappingURL=rpc-verify.js.map