bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
213 lines • 10.5 kB
JavaScript
#!/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