From b0456c3bfc930361872a2664067efa614ccfc955 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 17 Aug 2024 18:36:25 +0530 Subject: [PATCH] add and handle further modifications and flags to handle skeleton functionality --- packages/blockchain/src/blockchain.ts | 32 +++++---- packages/blockchain/src/db/manager.ts | 30 ++++++-- packages/blockchain/src/db/operation.ts | 2 + packages/blockchain/src/types.ts | 5 +- packages/client/src/service/skeleton.ts | 94 +++---------------------- 5 files changed, 58 insertions(+), 105 deletions(-) diff --git a/packages/blockchain/src/blockchain.ts b/packages/blockchain/src/blockchain.ts index 95b16d096a..d2d3c5d5a3 100644 --- a/packages/blockchain/src/blockchain.ts +++ b/packages/blockchain/src/blockchain.ts @@ -25,6 +25,7 @@ import { import { DBManager } from './db/manager.js' import { DBTarget } from './db/operation.js' +import type { OptimisticOpts } from './db/operation.js' import type { BlockchainEvents, BlockchainInterface, @@ -272,8 +273,8 @@ export class Blockchain implements BlockchainInterface { * heads/hashes are overwritten. * @param block - The block to be added to the blockchain */ - async putBlock(block: Block, optimistic: boolean = false) { - await this._putBlockOrHeader(block, optimistic) + async putBlock(block: Block, opts?: OptimisticOpts) { + await this._putBlockOrHeader(block, opts) } /** @@ -344,7 +345,7 @@ export class Blockchain implements BlockchainInterface { * header using the iterator method. * @hidden */ - private async _putBlockOrHeader(item: Block | BlockHeader, optimistic: boolean = false) { + private async _putBlockOrHeader(item: Block | BlockHeader, optimisticOpts?: OptimisticOpts) { await this.runWithLock(async () => { // Save the current sane state incase _putBlockOrHeader midway with some // dirty changes in head trackers @@ -362,13 +363,13 @@ export class Blockchain implements BlockchainInterface { if (isGenesis) { if (equalsBytes(this.genesisBlock.hash(), block.hash())) { // Try to re-put the existing genesis block, accept this - optimistic = false + // genesis block is not optimistic + optimisticOpts = undefined return } throw new Error( 'Cannot put a different genesis block than current blockchain genesis: create a new Blockchain', ) - // genesis block is not optimistic } if (block.common.chainId() !== this.common.chainId()) { @@ -377,12 +378,12 @@ export class Blockchain implements BlockchainInterface { ) } - if (this._validateBlocks && !isGenesis && item instanceof Block) { + if (this._validateBlocks && !isGenesis && item instanceof Block && optimisticOpts === undefined) { // this calls into `getBlock`, which is why we cannot lock yet await this.validateBlock(block) } - if (this._validateConsensus) { + if (this._validateConsensus && optimisticOpts === undefined) { await this.consensus!.validateConsensus(block) } @@ -397,20 +398,20 @@ export class Blockchain implements BlockchainInterface { if (!block.isGenesis()) { td += parentTd } - // since its linked its no more optimistic - optimistic = false } catch (e) { // opimistic insertion does care about td - if (!optimistic) { + if (optimisticOpts === undefined) { throw e } } let dbOps: DBOp[] = [] - if (optimistic) { + if (optimisticOpts !== undefined) { dbOps = dbOps.concat(DBSetBlockOrHeader(item)) dbOps.push(DBSetHashToNumber(blockHash, blockNumber)) - dbOps.push(DBOp.set(DBTarget.OptimisticNumberToHash, blockHash, { blockNumber })) + if (optimisticOpts.fcUed) { + dbOps.push(DBOp.set(DBTarget.OptimisticNumberToHash, blockHash, { blockNumber })) + } await this.dbManager.batch(dbOps) } else { const currentTd = { header: BIGINT_0, block: BIGINT_0 } @@ -676,13 +677,16 @@ export class Blockchain implements BlockchainInterface { * this will be immediately looked up, otherwise it will wait until we have * unlocked the DB */ - async getBlock(blockId: Uint8Array | number | bigint): Promise { + async getBlock( + blockId: Uint8Array | number | bigint, + optimisticOpts?: OptimisticOpts, + ): Promise { // cannot wait for a lock here: it is used both in `validate` of `Block` // (calls `getBlock` to get `parentHash`) it is also called from `runBlock` // in the `VM` if we encounter a `BLOCKHASH` opcode: then a bigint is used we // need to then read the block from the canonical chain Q: is this safe? We // know it is OK if we call it from the iterator... (runBlock) - const block = await this.dbManager.getBlock(blockId) + const block = await this.dbManager.getBlock(blockId, optimisticOpts) if (block === undefined) { if (typeof blockId === 'object') { diff --git a/packages/blockchain/src/db/manager.ts b/packages/blockchain/src/db/manager.ts index f0b1c202af..c34a222c82 100644 --- a/packages/blockchain/src/db/manager.ts +++ b/packages/blockchain/src/db/manager.ts @@ -12,7 +12,7 @@ import { import { Cache } from './cache.js' import { DBOp, DBTarget } from './operation.js' -import type { DatabaseKey } from './operation.js' +import type { DatabaseKey, OptimisticOpts } from './operation.js' import type { Block, BlockBodyBytes, BlockBytes, BlockOptions } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' import type { BatchDBOp, DB, DBObject, DelBatch, PutBatch } from '@ethereumjs/util' @@ -47,6 +47,7 @@ export class DBManager { body: new Cache({ max: 256 }), numberToHash: new Cache({ max: 2048 }), hashToNumber: new Cache({ max: 2048 }), + optimisticNumberToHash: new Cache({ max: 2048 }), } } @@ -86,7 +87,7 @@ export class DBManager { */ async getBlock( blockId: Uint8Array | bigint | number, - optimistic: boolean = false, + optimisticOpts?: OptimisticOpts, ): Promise { if (typeof blockId === 'number' && Number.isInteger(blockId)) { blockId = BigInt(blockId) @@ -98,13 +99,30 @@ export class DBManager { if (blockId instanceof Uint8Array) { hash = blockId number = await this.hashToNumber(blockId) + if (number === undefined) { + return undefined + } + + if (optimisticOpts?.fcUed === true) { + let optimisticHash = await this.optimisticNumberToHash(number) + if (optimisticHash === undefined && optimisticOpts.linked === true) { + optimisticHash = await this.numberToHash(number) + } + if (optimisticHash === undefined || !equalsBytes(optimisticHash, hash)) { + return undefined + } + } } else if (typeof blockId === 'bigint') { number = blockId - if (optimistic) { + if (optimisticOpts !== undefined) { + if (!optimisticOpts.fcUed) { + throw Error(`Invalid fcUed optimistic block by number lookup`) + } hash = await this.optimisticNumberToHash(blockId) - } - // hash will be undefined if it no optimistic lookup was done or if that was not successful - if (hash === undefined) { + if (hash === undefined && optimisticOpts.linked === true) { + hash = await this.numberToHash(blockId) + } + } else { hash = await this.numberToHash(blockId) } } else { diff --git a/packages/blockchain/src/db/operation.ts b/packages/blockchain/src/db/operation.ts index 5358d4c3bf..5dd3e40d54 100644 --- a/packages/blockchain/src/db/operation.ts +++ b/packages/blockchain/src/db/operation.ts @@ -14,6 +14,8 @@ import { import type { CacheMap } from './manager.js' +export type OptimisticOpts = { fcUed: boolean; linked?: boolean } + export enum DBTarget { Heads, HeadHeader, diff --git a/packages/blockchain/src/types.ts b/packages/blockchain/src/types.ts index c46debba28..f131ec7d23 100644 --- a/packages/blockchain/src/types.ts +++ b/packages/blockchain/src/types.ts @@ -1,3 +1,4 @@ +import type { OptimisticOpts } from './db/operation.js' import type { Blockchain } from './index.js' import type { Block, BlockHeader } from '@ethereumjs/block' import type { Common, ConsensusAlgorithm } from '@ethereumjs/common' @@ -16,7 +17,7 @@ export interface BlockchainInterface { * * @param block - The block to be added to the blockchain. */ - putBlock(block: Block): Promise + putBlock(block: Block, optimisticOpts?: OptimisticOpts): Promise /** * Deletes a block from the blockchain. All child blocks in the chain are @@ -29,7 +30,7 @@ export interface BlockchainInterface { /** * Returns a block by its hash or number. */ - getBlock(blockId: Uint8Array | number | bigint): Promise + getBlock(blockId: Uint8Array | number | bigint, optimisticOpts?: OptimisticOpts): Promise /** * Iterates through blocks starting at the specified iterator head and calls diff --git a/packages/client/src/service/skeleton.ts b/packages/client/src/service/skeleton.ts index 346dfe407e..5850e3eb0d 100644 --- a/packages/client/src/service/skeleton.ts +++ b/packages/client/src/service/skeleton.ts @@ -1362,21 +1362,7 @@ export class Skeleton extends MetaDBManager { */ private async putBlock(block: Block, onlyUnfinalized: boolean = false): Promise { // Serialize the block with its hardfork so that its easy to load the block latter - const rlp = this.serialize({ hardfork: block.common.hardfork(), blockRLP: block.serialize() }) - await this.put(DBKey.SkeletonUnfinalizedBlockByHash, block.hash(), rlp) - - if (!onlyUnfinalized) { - await this.put(DBKey.SkeletonBlock, bigIntToBytes(block.header.number), rlp) - // this is duplication of the unfinalized blocks but for now an easy reference - // will be pruned on finalization changes. this could be simplified and deduped - // but will anyway will move into blockchain class and db on upcoming skeleton refactor - await this.put( - DBKey.SkeletonBlockHashToNumber, - block.hash(), - bigIntToBytes(block.header.number), - ) - } - + await this.chain.blockchain.putBlock(block, { fcUed: !onlyUnfinalized }) return true } @@ -1395,22 +1381,10 @@ export class Skeleton extends MetaDBManager { * Gets a block from the skeleton or canonical db by number. */ async getBlock(number: bigint, onlyCanonical = false): Promise { - try { - const skeletonBlockRlp = await this.get(DBKey.SkeletonBlock, bigIntToBytes(number)) - if (skeletonBlockRlp === null) { - throw Error(`SkeletonBlock rlp lookup failed for ${number} onlyCanonical=${onlyCanonical}`) - } - return this.skeletonBlockRlpToBlock(skeletonBlockRlp) - } catch (error: any) { - // If skeleton is linked, it probably has deleted the block and put it into the chain - if (onlyCanonical && !this.status.linked) return undefined - // As a fallback, try to get the block from the canonical chain in case it is available there - try { - return await this.chain.getBlock(number) - } catch (error) { - return undefined - } - } + return this.chain.blockchain.dbManager.getBlock(number, { + fcUed: onlyCanonical, + linked: this.status.linked, + }) } /** @@ -1420,63 +1394,17 @@ export class Skeleton extends MetaDBManager { hash: Uint8Array, onlyCanonical: boolean = false, ): Promise { - const number = await this.get(DBKey.SkeletonBlockHashToNumber, hash) - if (number) { - const block = await this.getBlock(bytesToBigInt(number), onlyCanonical) - if (block !== undefined && equalsBytes(block.hash(), hash)) { - return block - } - } - - if (onlyCanonical === true && !this.status.linked) { - return undefined - } - - let block = onlyCanonical === false ? await this.getUnfinalizedBlock(hash) : undefined - if (block === undefined && (onlyCanonical === false || this.status.linked)) { - block = await this.chain.getBlock(hash).catch((_e) => undefined) - } - - if (onlyCanonical === false) { - return block - } else { - if (this.status.linked && block !== undefined) { - const canBlock = await this.chain.getBlock(block.header.number).catch((_e) => undefined) - if (canBlock !== undefined && equalsBytes(canBlock.hash(), block.hash())) { - // block is canonical - return block - } - } - - // no canonical block found or the block was not canonical - return undefined - } - } - - async getUnfinalizedBlock(hash: Uint8Array): Promise { - try { - const skeletonBlockRlp = await this.get(DBKey.SkeletonUnfinalizedBlockByHash, hash) - if (skeletonBlockRlp === null) { - throw Error(`SkeletonUnfinalizedBlockByHash rlp lookup failed for hash=${short(hash)}`) - } - return this.skeletonBlockRlpToBlock(skeletonBlockRlp) - } catch (_e) { - return undefined - } + return this.chain.blockchain.dbManager.getBlock(hash, { + fcUed: !onlyCanonical, + linked: this.status.linked, + }) } /** * Deletes a skeleton block from the db by number */ - async deleteBlock(block: Block): Promise { - try { - await this.delete(DBKey.SkeletonBlock, bigIntToBytes(block.header.number)) - await this.delete(DBKey.SkeletonBlockHashToNumber, block.hash()) - await this.delete(DBKey.SkeletonUnfinalizedBlockByHash, block.hash()) - return true - } catch (error: any) { - return false - } + async deleteBlock(_block: Block): Promise { + return true } /**