bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
399 lines • 16.6 kB
JavaScript
;
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