UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

399 lines 16.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.VerificationPeer = void 0; const _ = __importStar(require("lodash")); const logger_1 = __importStar(require("../../logger")); const block_1 = require("../../models/block"); const coin_1 = require("../../models/coin"); const transaction_1 = require("../../models/transaction"); const p2p_1 = require("../../modules/bitcoin/p2p"); const chain_state_1 = require("../../providers/chain-state"); class VerificationPeer extends p2p_1.BitcoinP2PWorker { constructor() { super(...arguments); this.prevBlockNum = 0; this.prevHash = ''; this.nextBlockHash = ''; this.deepScan = false; } enableDeepScan() { this.deepScan = true; } disableDeepScan() { this.deepScan = false; } setupListeners() { this.pool.on('peerready', peer => { logger_1.default.info(`${(0, logger_1.timestamp)()} | Connected to peer: ${peer.host}:${peer.port.toString().padEnd(5)} | Chain: ${this.chain} | Network: ${this.network}`); }); this.pool.on('peerdisconnect', peer => { logger_1.default.warn(`${(0, logger_1.timestamp)()} | Not connected to peer: ${peer.host}:${peer.port.toString().padEnd(5)} | Chain: ${this.chain} | Network: ${this.network}`); }); this.pool.on('peertx', async (peer, message) => { const hash = message.transaction.hash; logger_1.default.debug('peer tx received: %o', { peer: `${peer.host}:${peer.port}`, chain: this.chain, network: this.network, hash }); this.events.emit('transaction', message.transaction); }); this.pool.on('peerblock', async (peer, message) => { const { block } = message; const { hash } = block; logger_1.default.debug('peer block received: %o', { peer: `${peer.host}:${peer.port}`, chain: this.chain, network: this.network, hash }); this.events.emit(hash, message.block); this.events.emit('block', message.block); }); this.pool.on('peerheaders', (peer, message) => { logger_1.default.debug('peerheaders message received: %o', { peer: `${peer.host}:${peer.port}`, chain: this.chain, network: this.network, count: message.headers.length }); this.events.emit('headers', message.headers); }); this.pool.on('peerinv', (peer, message) => { const filtered = message.inventory.filter(inv => { const hash = this.bitcoreLib.encoding .BufferReader(inv.hash) .readReverse() .toString('hex'); return !this.isCachedInv(inv.type, hash); }); if (filtered.length) { peer.sendMessage(this.messages.GetData(filtered)); } }); } async getBlockForNumber(currentHeight) { const { chain, network } = this; const [{ hash }] = await block_1.BitcoinBlockStorage.collection .find({ chain, network, height: currentHeight }) .limit(1) .toArray(); return this.getBlock(hash); } async resync(start, end) { const { chain, network } = this; let currentHeight = Math.max(1, start); while (currentHeight < end) { const locatorHashes = await chain_state_1.ChainStateProvider.getLocatorHashes({ chain, network, startHeight: Math.max(1, currentHeight - 30), endHeight: currentHeight }); const headers = await this.getHeaders(locatorHashes); if (!headers.length) { logger_1.default.info(`${chain}:${network} up to date.`); break; } const headerCount = Math.min(headers.length, end - currentHeight); logger_1.default.info(`Re-Syncing ${headerCount} blocks for ${chain} ${network}`); let lastLog = Date.now(); for (let header of headers) { if (currentHeight <= end) { const block = await this.getBlock(header.hash); await block_1.BitcoinBlockStorage.processBlock({ chain, network, block, initialSyncComplete: true }); const nextBlock = await block_1.BitcoinBlockStorage.collection.findOne({ chain, network, previousBlockHash: block.hash }); if (nextBlock) { await block_1.BitcoinBlockStorage.collection.updateOne({ chain, network, hash: block.hash }, { $set: { nextBlockHash: nextBlock.hash } }); } } currentHeight++; if (Date.now() - lastLog > 100) { logger_1.default.info('Re-Sync: %o', { chain, network, height: currentHeight }); lastLog = Date.now(); } } } } async validateDataForBlock(blockNum, tipHeight, log = false) { let success = true; const { chain, network } = this; const atTipOfChain = blockNum === tipHeight; const errors = new Array(); const [block, blockTxs] = await Promise.all([ block_1.BitcoinBlockStorage.collection.findOne({ chain, network, height: blockNum, processed: true }), transaction_1.TransactionStorage.collection.find({ chain, network, blockHeight: blockNum }).toArray() ]); if (!block) { success = false; const error = { model: 'block', err: true, type: 'MISSING_BLOCK', payload: { blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } return { success, errors }; } const blockTxids = blockTxs.map(t => t.txid); const firstHash = blockTxs[0] ? blockTxs[0].blockHash : block.hash; const [coinsForTx, mempoolTxs, blocksForHash, blocksForHeight, p2pBlock] = await Promise.all([ coin_1.CoinStorage.collection.find({ chain, network, mintTxid: { $in: blockTxids } }).toArray(), transaction_1.TransactionStorage.collection.find({ chain, network, blockHeight: -1, txid: { $in: blockTxids } }).toArray(), block_1.BitcoinBlockStorage.collection.countDocuments({ chain, network, hash: firstHash }), block_1.BitcoinBlockStorage.collection.countDocuments({ chain, network, height: blockNum, processed: true }), this.deepScan ? this.getBlockForNumber(blockNum) : Promise.resolve({}) ]); const seenTxs = {}; const linearProgress = this.prevBlockNum && this.prevBlockNum == blockNum - 1; const prevHashMismatch = this.prevHash && block.previousBlockHash != this.prevHash; const nextHashMismatch = this.nextBlockHash && block.hash != this.nextBlockHash; this.prevHash = block.hash; this.nextBlockHash = block.nextBlockHash; this.prevBlockNum = blockNum; const missingLinearData = linearProgress && (prevHashMismatch || nextHashMismatch); const missingNextBlockHash = !atTipOfChain && !block.nextBlockHash; const missingPrevBlockHash = !block.previousBlockHash; const missingData = missingNextBlockHash || missingPrevBlockHash || missingLinearData; if (!block || block.transactionCount != blockTxs.length || missingData) { success = false; const error = { model: 'block', err: true, type: 'CORRUPTED_BLOCK', payload: { blockNum, txCount: block.transactionCount, foundTxs: blockTxs.length } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } if (block && this.deepScan && p2pBlock) { const txs = p2pBlock.transactions ? p2pBlock.transactions.slice(1) : []; const spends = _.chain(txs) .map(tx => tx.inputs) .flatten() .map(input => input.toObject()) .value(); for (let spend of spends) { const found = await coin_1.CoinStorage.collection.findOne({ chain, network, mintTxid: spend.prevTxId, mintIndex: spend.outputIndex }); if (found && found.spentHeight !== block.height) { success = false; const error = { model: 'coin', err: true, type: 'COIN_SHOULD_BE_SPENT', payload: { coin: found, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } else { if (!found && spend.prevTxId != '0000000000000000000000000000000000000000000000000000000000000000') { success = false; const error = { model: 'coin', err: true, type: 'MISSING_INPUT', payload: { coin: { mintTxid: spend.prevTxId, mintIndex: spend.outputIndex }, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } } } } for (let tx of mempoolTxs) { success = false; const error = { model: 'transaction', err: true, type: 'DUPE_TRANSACTION', payload: { tx, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } const seenTxCoins = {}; for (let tx of blockTxs) { if (tx.fee < 0) { success = false; const error = { model: 'transaction', err: true, type: 'NEG_FEE', payload: { tx, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } if (seenTxs[tx.txid]) { success = false; const error = { model: 'transaction', err: true, type: 'DUPE_TRANSACTION', payload: { tx, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } else { seenTxs[tx.txid] = tx; } } for (let coin of coinsForTx) { if (seenTxCoins[coin.mintTxid] && seenTxCoins[coin.mintTxid][coin.mintIndex]) { success = false; const error = { model: 'coin', err: true, type: 'DUPE_COIN', payload: { coin, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } else { seenTxCoins[coin.mintTxid] = seenTxCoins[coin.mintTxid] || {}; seenTxCoins[coin.mintTxid][coin.mintIndex] = coin; } } const mintHeights = _.uniq(coinsForTx.map(c => c.mintHeight)); if (mintHeights.length > 1) { success = false; const error = { model: 'coin', err: true, type: 'COIN_HEIGHT_MISMATCH', payload: { blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } for (let txid of Object.keys(seenTxs)) { const coins = seenTxCoins[txid]; if (!coins) { success = false; const error = { model: 'coin', err: true, type: 'MISSING_COIN_FOR_TXID', payload: { txid, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } } for (let txid of Object.keys(seenTxCoins)) { const tx = seenTxs[txid]; const coins = seenTxCoins[txid]; if (!tx) { success = false; const error = { model: 'transaction', err: true, type: 'MISSING_TX', payload: { txid, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } else { const sum = Object.values(coins).reduce((prev, cur) => prev + cur.value, 0); if (sum != tx.value) { success = false; const error = { model: 'coin+transactions', err: true, type: 'VALUE_MISMATCH', payload: { tx, coins, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } } } if (blocksForHeight === 0) { success = false; const error = { model: 'block', err: true, type: 'MISSING_BLOCK', payload: { blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } if (blocksForHeight > 1) { success = false; const error = { model: 'block', err: true, type: 'DUPE_BLOCKHEIGHT', payload: { blockNum, blocksForHeight } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } // blocks with same hash if (blockTxs.length > 0) { const hashFromTx = blockTxs[0].blockHash; if (blocksForHash > 1) { success = false; const error = { model: 'block', err: true, type: 'DUPE_BLOCKHASH', payload: { hash: hashFromTx, blockNum } }; errors.push(error); if (log) { console.log(JSON.stringify(error)); } } } return { success, errors }; } } exports.VerificationPeer = VerificationPeer; //# sourceMappingURL=VerificationPeer.js.map