@ethereumjs/blockchain
Version:
A module to store and interact with blocks
203 lines • 8.3 kB
JavaScript
;
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