diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 520b20b820fc..39f1e49e987c 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -301,6 +301,10 @@ export class BeaconChain implements IBeaconChain { await this.bls.close(); } + seenBlock(blockRoot: RootHex): boolean { + return this.seenGossipBlockInput.hasBlock(blockRoot) || this.forkChoice.hasBlockHex(blockRoot); + } + regenCanAcceptWork(): boolean { return this.regen.canAcceptWork(); } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 6e932b25c50e..0771a01caf08 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -119,6 +119,8 @@ export interface IBeaconChain { /** Stop beacon chain processing */ close(): Promise; + /** Chain has seen the specified block root or not. The block may not be processed yet, use forkchoice.hasBlock to check it */ + seenBlock(blockRoot: RootHex): boolean; /** Populate in-memory caches with persisted data. Call at least once on startup */ loadFromDisk(): Promise; /** Persist in-memory data to the DB. Call at least once before stopping the process */ diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 8b767975c112..1f23503d3957 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -2,7 +2,7 @@ import {toHexString} from "@chainsafe/ssz"; import {deneb, RootHex, ssz, allForks} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {pruneSetToMax} from "@lodestar/utils"; -import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params"; +import {BLOBSIDECAR_FIXED_SIZE, ForkSeq} from "@lodestar/params"; import { BlockInput, @@ -29,9 +29,11 @@ type BlockInputCacheType = { const MAX_GOSSIPINPUT_CACHE = 5; /** - * SeenGossipBlockInput tracks and caches the live blobs and blocks on the network to solve data availability - * for the blockInput. If no block has been seen yet for some already seen blobs, it responds will null, but - * on the first block or the consequent blobs it responds with blobs promise till all blobs become available. + * For predeneb, SeenGossipBlockInput only tracks and caches block so that we don't need to download known block + * roots. From deneb, it serves same purpose plus tracks and caches the live blobs and blocks on the network to + * solve data availability for the blockInput. If no block has been seen yet for some already seen blobs, it + * responds will null, but on the first block or the consequent blobs it responds with blobs promise till all blobs + * become available. * * One can start processing block on blobs promise blockInput response and can await on the promise before * fully importing the block. The blobs promise is gets resolved as soon as all blobs corresponding to that @@ -44,6 +46,10 @@ export class SeenGossipBlockInput { pruneSetToMax(this.blockInputCache, MAX_GOSSIPINPUT_CACHE); } + hasBlock(blockRoot: RootHex): boolean { + return this.blockInputCache.has(blockRoot); + } + getGossipBlockInput( config: ChainForkConfig, gossipedInput: GossipedBlockInput @@ -83,9 +89,16 @@ export class SeenGossipBlockInput { if (!this.blockInputCache.has(blockHex)) { this.blockInputCache.set(blockHex, blockCache); } + const {block: signedBlock, blockBytes, blobsCache, availabilityPromise, resolveAvailability} = blockCache; if (signedBlock !== undefined) { + if (config.getForkSeq(signedBlock.message.slot) < ForkSeq.deneb) { + return { + blockInput: getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, blockBytes ?? null), + blockInputMeta: {pending: null, haveBlobs: 0, expectedBlobs: 0}, + }; + } // block is available, check if all blobs have shown up const {slot, body} = signedBlock.message; const {blobKzgCommitments} = body as deneb.BeaconBlockBody; diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index d31183828b85..9073a204b785 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -45,13 +45,7 @@ import {PeerAction} from "../peers/index.js"; import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js"; import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js"; import {validateGossipBlobSidecar} from "../../chain/validation/blobSidecar.js"; -import { - BlockInput, - BlockSource, - getBlockInput, - GossipedInputType, - BlobSidecarValidation, -} from "../../chain/blocks/types.js"; +import {BlockInput, GossipedInputType, BlobSidecarValidation} from "../../chain/blocks/types.js"; import {sszDeserialize} from "../gossip/topic.js"; import {INetworkCore} from "../core/index.js"; import {INetwork} from "../interface.js"; @@ -123,28 +117,21 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec); const recvToVal = Date.now() / 1000 - seenTimestampSec; - let blockInput; - let blockInputMeta; - if (config.getForkSeq(signedBlock.message.slot) >= ForkSeq.deneb) { - const blockInputRes = chain.seenGossipBlockInput.getGossipBlockInput(config, { - type: GossipedInputType.block, - signedBlock, - blockBytes, - }); - - blockInput = blockInputRes.blockInput; - blockInputMeta = blockInputRes.blockInputMeta; - - // blockInput can't be returned null, improve by enforcing via return types - if (blockInput === null) { - throw Error( - `Invalid null blockInput returned by getGossipBlockInput for type=${GossipedInputType.block} blockHex=${blockHex} slot=${slot}` - ); - } - } else { - blockInput = getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, blockBytes); - blockInputMeta = {}; + // always set block to seen cache for all forks so that we don't need to download it + const blockInputRes = chain.seenGossipBlockInput.getGossipBlockInput(config, { + type: GossipedInputType.block, + signedBlock, + blockBytes, + }); + const blockInput = blockInputRes.blockInput; + // blockInput can't be returned null, improve by enforcing via return types + if (blockInput === null) { + throw Error( + `Invalid null blockInput returned by getGossipBlockInput for type=${GossipedInputType.block} blockHex=${blockHex} slot=${slot}` + ); } + const blockInputMeta = + config.getForkSeq(signedBlock.message.slot) >= ForkSeq.deneb ? blockInputRes.blockInputMeta : {}; metrics?.gossipBlock.receivedToGossipValidate.observe(recvToVal); logger.verbose("Received gossip block", { diff --git a/packages/beacon-node/src/network/processor/index.ts b/packages/beacon-node/src/network/processor/index.ts index 3d067c626f76..6fd0b235dfb1 100644 --- a/packages/beacon-node/src/network/processor/index.ts +++ b/packages/beacon-node/src/network/processor/index.ts @@ -231,11 +231,12 @@ export class NetworkProcessor { } searchUnknownSlotRoot({slot, root}: SlotRootHex, peer?: PeerIdStr): void { - // Search for the unknown block - if (!this.unknownRootsBySlot.getOrDefault(slot).has(root)) { - this.unknownRootsBySlot.getOrDefault(slot).add(root); - this.events.emit(NetworkEvent.unknownBlock, {rootHex: root, peer}); + if (this.chain.seenBlock(root) || this.unknownRootsBySlot.getOrDefault(slot).has(root)) { + return; } + // Search for the unknown block + this.unknownRootsBySlot.getOrDefault(slot).add(root); + this.events.emit(NetworkEvent.unknownBlock, {rootHex: root, peer}); } private onPendingGossipsubMessage(message: PendingGossipsubMessage): void {