UNPKG

@ethereumjs/blockchain

Version:
203 lines 8.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DBManager = void 0; const block_1 = require("@ethereumjs/block"); const rlp_1 = require("@ethereumjs/rlp"); const util_1 = require("@ethereumjs/util"); const cache_ts_1 = require("./cache.js"); const operation_ts_1 = require("./operation.js"); /** * Abstraction over a DB to facilitate storing/fetching blockchain-related * data, such as blocks and headers, indices, and the head block. * @hidden */ class DBManager { constructor(db, common) { this._db = db; this.common = common; this._cache = { td: new cache_ts_1.Cache({ max: 1024 }), header: new cache_ts_1.Cache({ max: 512 }), body: new cache_ts_1.Cache({ max: 256 }), numberToHash: new cache_ts_1.Cache({ max: 2048 }), hashToNumber: new cache_ts_1.Cache({ max: 2048 }), }; } /** * Fetches iterator heads from the db. */ async getHeads() { const heads = (await this.get(operation_ts_1.DBTarget.Heads)); if (heads === undefined) return heads; const decodedHeads = {}; for (const key of Object.keys(heads)) { // Heads are stored in DB as hex strings since Level converts Uint8Arrays // to nested JSON objects when they are included in a value being stored // in the DB decodedHeads[key] = (0, util_1.unprefixedHexToBytes)(heads[key]); } return decodedHeads; } /** * Fetches header of the head block. */ async getHeadHeader() { return this.get(operation_ts_1.DBTarget.HeadHeader); } /** * Fetches head block. */ async getHeadBlock() { return this.get(operation_ts_1.DBTarget.HeadBlock); } /** * Fetches a block (header and body) given a block id, * which can be either its hash or its number. */ async getBlock(blockId) { if (typeof blockId === 'number' && Number.isInteger(blockId)) { blockId = BigInt(blockId); } let number; let hash; if (blockId === undefined) return undefined; if (blockId instanceof Uint8Array) { hash = blockId; number = await this.hashToNumber(blockId); } else if (typeof blockId === 'bigint') { number = blockId; hash = await this.numberToHash(blockId); } else { throw (0, util_1.EthereumJSErrorWithoutCode)('Unknown blockId type'); } if (hash === undefined || number === undefined) return undefined; const header = await this.getHeader(hash, number); let body = await this.getBody(hash, number); // be backward compatible where we didn't use to store a body with no txs, uncles, withdrawals // otherwise the body is never partially stored and if we have some body, its in entirety if (body === undefined) { body = [[], []]; // Do extra validations on the header since we are assuming empty transactions and uncles if (!(0, util_1.equalsBytes)(header.transactionsTrie, util_1.KECCAK256_RLP)) { throw (0, util_1.EthereumJSErrorWithoutCode)('transactionsTrie root should be equal to hash of null'); } if (!(0, util_1.equalsBytes)(header.uncleHash, util_1.KECCAK256_RLP_ARRAY)) { throw (0, util_1.EthereumJSErrorWithoutCode)('uncle hash should be equal to hash of empty array'); } // If this block had empty withdrawals push an empty array in body if (header.withdrawalsRoot !== undefined) { // Do extra validations for withdrawal before assuming empty withdrawals if (!(0, util_1.equalsBytes)(header.withdrawalsRoot, util_1.KECCAK256_RLP)) { throw (0, util_1.EthereumJSErrorWithoutCode)('withdrawals root shoot be equal to hash of null when no withdrawals'); } else { body.push([]); } } } const blockData = [header.raw(), ...body]; const opts = { common: this.common, setHardfork: true }; return (0, block_1.createBlockFromBytesArray)(blockData, opts); } /** * Fetches body of a block given its hash and number. */ async getBody(blockHash, blockNumber) { const body = await this.get(operation_ts_1.DBTarget.Body, { blockHash, blockNumber }); return body !== undefined ? rlp_1.RLP.decode(body) : undefined; } /** * Fetches header of a block given its hash and number. */ async getHeader(blockHash, blockNumber) { const encodedHeader = await this.get(operation_ts_1.DBTarget.Header, { blockHash, blockNumber }); const headerValues = rlp_1.RLP.decode(encodedHeader); const opts = { common: this.common, setHardfork: true }; return (0, block_1.createBlockHeaderFromBytesArray)(headerValues, opts); } /** * Fetches total difficulty for a block given its hash and number. */ async getTotalDifficulty(blockHash, blockNumber) { const td = await this.get(operation_ts_1.DBTarget.TotalDifficulty, { blockHash, blockNumber }); return (0, util_1.bytesToBigInt)(rlp_1.RLP.decode(td)); } /** * Performs a block hash to block number lookup. */ async hashToNumber(blockHash) { const value = await this.get(operation_ts_1.DBTarget.HashToNumber, { blockHash }); if (value === undefined) { throw (0, util_1.EthereumJSErrorWithoutCode)(`value for ${(0, util_1.bytesToHex)(blockHash)} not found in DB`); } return value !== undefined ? (0, util_1.bytesToBigInt)(value) : undefined; } /** * Performs a block number to block hash lookup. */ async numberToHash(blockNumber) { const value = await this.get(operation_ts_1.DBTarget.NumberToHash, { blockNumber }); return value; } /** * Fetches a key from the db. If `opts.cache` is specified * it first tries to load from cache, and on cache miss will * try to put the fetched item on cache afterwards. */ async get(dbOperationTarget, key) { const dbGetOperation = operation_ts_1.DBOp.get(dbOperationTarget, key); const cacheString = dbGetOperation.cacheString; const dbKey = dbGetOperation.baseDBOp.key; if (cacheString !== undefined) { if (this._cache[cacheString] === undefined) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid cache: ${cacheString}`); } let value = this._cache[cacheString].get(dbKey); if (value === undefined) { value = (await this._db.get(dbKey, { keyEncoding: dbGetOperation.baseDBOp.keyEncoding, valueEncoding: dbGetOperation.baseDBOp.valueEncoding, })); if (value !== undefined) { this._cache[cacheString].set(dbKey, value); } } return value; } return this._db.get(dbKey, { keyEncoding: dbGetOperation.baseDBOp.keyEncoding, valueEncoding: dbGetOperation.baseDBOp.valueEncoding, }); } /** * Performs a batch operation on db. */ async batch(ops) { const convertedOps = ops.map((op) => { const type = op.baseDBOp.type ?? (op.baseDBOp.value !== undefined ? 'put' : 'del'); const convertedOp = { key: op.baseDBOp.key, value: op.baseDBOp.value, type, opts: { keyEncoding: op.baseDBOp.keyEncoding, valueEncoding: op.baseDBOp.valueEncoding, }, }; if (type === 'put') return convertedOp; else return convertedOp; }); // update the current cache for each operation ops.map((op) => op.updateCache(this._cache)); return this._db.batch(convertedOps); } } exports.DBManager = DBManager; //# sourceMappingURL=manager.js.map