UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

250 lines 11.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EVMBlockStorage = exports.EVMBlockModel = void 0; const bson_1 = require("bson"); const Loggify_1 = require("../../../../decorators/Loggify"); const logger_1 = __importDefault(require("../../../../logger")); const baseBlock_1 = require("../../../../models/baseBlock"); const events_1 = require("../../../../models/events"); const transaction_1 = require("./transaction"); let EVMBlockModel = class EVMBlockModel extends baseBlock_1.BaseBlock { constructor(storage) { super(storage); } async onConnect() { super.onConnect(); } async addBlock(params) { // TODO: add skipReorg to config for development purposes const { block, chain, network } = params; let reorg = false; if (params.initialSyncComplete) { const headers = await this.validateLocatorHashes({ chain, network }); if (headers.length) { const last = headers[headers.length - 1]; reorg = await this.handleReorg({ block: last, chain, network }); } reorg = reorg || (await this.handleReorg({ block, chain, network })); } if (reorg) { return Promise.reject('reorg'); } return this.processBlock(params); } async processBlock(params) { const { chain, network, transactions, parentChain, forkHeight, initialSyncComplete } = params; const blockOp = await this.getBlockOp(params); const convertedBlock = blockOp.updateOne.update.$set; const { height, timeNormalized, time } = convertedBlock; // Put in the transactions first await transaction_1.EVMTransactionStorage.batchImport({ txs: transactions, blockHash: convertedBlock.hash, blockTime: new Date(time), blockTimeNormalized: new Date(timeNormalized), height, chain, network, parentChain, forkHeight, initialSyncComplete }); const previousBlock = await this.collection.findOne({ hash: convertedBlock.previousBlockHash, chain, network }); await this.collection.bulkWrite([blockOp]); if (previousBlock) { await this.collection.updateOne({ chain, network, hash: previousBlock.hash }, { $set: { nextBlockHash: convertedBlock.hash } }); logger_1.default.debug('Updating previous block.nextBlockHash: %o', convertedBlock.hash); } if (initialSyncComplete) { events_1.EventStorage.signalBlock(convertedBlock); } await this.collection.updateOne({ hash: convertedBlock.hash, chain, network }, { $set: { processed: true } }); } async getBlockOp(params) { const { block, chain, network } = params; const blockTime = block.time; const prevHash = block.previousBlockHash; const previousBlock = await this.collection.findOne({ hash: prevHash, chain, network }); const timeNormalized = (() => { const prevTime = previousBlock ? new Date(previousBlock.timeNormalized) : null; if (prevTime && blockTime.getTime() <= prevTime.getTime()) { return new Date(prevTime.getTime() + 1); } else { return blockTime; } })(); const height = block.height; logger_1.default.debug('Setting blockheight: %o', height); return { updateOne: { filter: { hash: block.hash, chain, network }, update: { $set: { ...block, timeNormalized } }, upsert: true } }; } async handleReorg(params) { const { block, chain, network } = params; const prevHash = block.previousBlockHash; let localTip = await this.getLocalTip(params); if (block != null && localTip != null && (localTip.hash === prevHash || localTip.hash === block.hash)) { return false; } if (!localTip || localTip.height === 0) { return false; } if (block) { const prevBlock = await this.collection.findOne({ chain, network, hash: prevHash }); if (prevBlock) { localTip = prevBlock; } else { logger_1.default.error("Previous block isn't in the DB need to roll back until we have a block in common"); } logger_1.default.info(`Resetting tip to ${localTip.height - 1}`, { chain, network }); } const reorgOps = [ this.collection.deleteMany({ chain, network, height: { $gte: localTip.height } }), transaction_1.EVMTransactionStorage.collection.deleteMany({ chain, network, blockHeight: { $gte: localTip.height } }) ]; await Promise.all(reorgOps); logger_1.default.debug('Removed data from above blockHeight: %o', localTip.height); return localTip.hash !== prevHash; } _apiTransform(block, options) { const transform = { _id: block._id, chain: block.chain, network: block.network, hash: block.hash, height: block.height, size: block.size, gasLimit: block.gasLimit, gasUsed: block.gasUsed, merkleRoot: block.merkleRoot, time: block.time, timeNormalized: block.timeNormalized, nonce: block.nonce, previousBlockHash: block.previousBlockHash, nextBlockHash: block.nextBlockHash, reward: block.reward, transactionCount: block.transactionCount, difficulty: block.difficulty, totalDifficulty: block.totalDifficulty }; if (options && options.object) { return transform; } return JSON.stringify(transform); } async getBlockSyncGaps(params) { const { chain, network, startHeight = 0, endHeight } = params; const self = this; return new Promise(async (resolve, reject) => { let timeout; try { const heightQuery = { $gte: startHeight }; if (endHeight) { heightQuery['$lte'] = endHeight; } const stream = self.collection .find({ chain, network, processed: true, height: heightQuery }) .sort({ chain: 1, network: 1, processed: 1, height: -1 }) // guarantee index use by using this sort .addCursorFlag('noCursorTimeout', true); let block = (await stream.next()); const maxBlock = block; if (!maxBlock) { return resolve([]); } const maxHeight = maxBlock.height; let prevBlock; const outOfSync = []; timeout = setInterval(() => logger_1.default.info(`${chain}:${network} Block verification progress: ${(((maxHeight - block.height) / (maxHeight - startHeight)) * 100).toFixed(1)}%`), 1000 * 2); // we are descending in blockHeight as we iterate for (let syncHeight = maxHeight; syncHeight >= startHeight; syncHeight--) { if (!block || block.height !== syncHeight) { outOfSync.push(syncHeight); } else { // prevBlock should be the next block up in height if (prevBlock && !block.nextBlockHash && block.height === prevBlock.height - 1) { const res = await self.collection.updateOne({ chain, network, hash: block.hash }, { $set: { nextBlockHash: prevBlock.hash } }); if (res.modifiedCount === 1) { block.nextBlockHash = prevBlock.hash; } } prevBlock = block; block = (await stream.next()); while (block && prevBlock && block.height === prevBlock.height) { // uncaught reorg? logger_1.default.error('Conflicting blocks found at height %o. %o <-> %o', block.height, block.hash, prevBlock.hash); block = (await stream.next()); } } } resolve(outOfSync.reverse()); // reverse order so that they are in ascending order } catch (err) { reject(err); } finally { clearTimeout(timeout); } }); } convertRawBlock(chain, network, block) { return { chain, network, height: block.number, hash: block.hash, coinbase: new bson_1.Binary(Buffer.from(block.miner)), merkleRoot: new bson_1.Binary(Buffer.from(block.transactionsRoot)), // TODO: rm `as any` if web3 is updated and fixes itself time: new Date(Number(block.timestamp) * 1000), timeNormalized: new Date(Number(block.timestamp) * 1000), nonce: new bson_1.Binary(Buffer.from(block.extraData)), previousBlockHash: block.parentHash, difficulty: block.difficulty.toString(), totalDifficulty: block.totalDifficulty?.toString(), nextBlockHash: '', transactionCount: block.transactions.length, size: block.size, reward: 0, logsBloom: new bson_1.Binary(Buffer.from(block.logsBloom)), sha3Uncles: new bson_1.Binary(Buffer.from(block.sha3Uncles)), receiptsRoot: new bson_1.Binary(Buffer.from(block.receiptsRoot)), processed: false, gasLimit: block.gasLimit, gasUsed: block.gasUsed, baseFeePerGas: block.baseFeePerGas, stateRoot: new bson_1.Binary(Buffer.from(block.stateRoot)), }; } }; exports.EVMBlockModel = EVMBlockModel; exports.EVMBlockModel = EVMBlockModel = __decorate([ Loggify_1.LoggifyClass ], EVMBlockModel); exports.EVMBlockStorage = new EVMBlockModel(); //# sourceMappingURL=block.js.map