diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts index dfd2f2889dee..6dda9588e859 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -5,6 +5,7 @@ import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep} from "@lodestar/utils"; import {fromHexString, toHexString} from "@chainsafe/ssz"; import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js"; +import {getBlockInput} from "../../../../chain/blocks/types.js"; import {OpSource} from "../../../../metrics/validatorMonitor.js"; import {NetworkEvent} from "../../../../network/index.js"; import {ApiModules, IS_OPTIMISTIC_TEMP} from "../../types.js"; @@ -186,14 +187,17 @@ export function getBeaconBlockApi({ metrics?.registerBeaconBlock(OpSource.api, seenTimestampSec, signedBlock.message); + // TODO EIP-4844: Will throw an error for blocks post EIP-4844 + const blockInput = getBlockInput.preEIP4844(config, signedBlock); + await Promise.all([ // Send the block, regardless of whether or not it is valid. The API // specification is very clear that this is the desired behaviour. network.gossip.publishBeaconBlock(signedBlock), - chain.processBlock(signedBlock).catch((e) => { + chain.processBlock(blockInput).catch((e) => { if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { - network.events.emit(NetworkEvent.unknownBlockParent, signedBlock, network.peerId.toString()); + network.events.emit(NetworkEvent.unknownBlockParent, blockInput, network.peerId.toString()); } throw e; }), diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index bd912df5ccb6..b55bdc696ea8 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -87,9 +87,9 @@ export function getLodestarApi({ async getBlockProcessorQueueItems() { return (chain as BeaconChain)["blockProcessor"].jobQueue.getItems().map((item) => { - const [blocks, opts] = item.args; + const [blockInputs, opts] = item.args; return { - blockSlots: blocks.map((block) => block.message.slot), + blockSlots: blockInputs.map((blockInput) => blockInput.block.message.slot), jobOpts: opts, addedTimeMs: item.addedTimeMs, }; diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 7fe28d7eb6ed..2765382e1f2a 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -48,7 +48,8 @@ export async function importBlock( fullyVerifiedBlock: FullyVerifiedBlock, opts: ImportBlockOpts ): Promise { - const {block, postState, parentBlockSlot, executionStatus} = fullyVerifiedBlock; + const {blockInput, postState, parentBlockSlot, executionStatus} = fullyVerifiedBlock; + const {block} = blockInput; const pendingEvents = new PendingEvents(this.emitter); // - Observe attestations @@ -312,6 +313,7 @@ export async function importBlock( // MUST happen before any other block is processed // This adds the state necessary to process the next block this.stateCache.add(postState); + await this.db.block.add(block); // - head_tracker.register_block(block_root, parent_root, slot) diff --git a/packages/beacon-node/src/chain/blocks/index.ts b/packages/beacon-node/src/chain/blocks/index.ts index 4c87a3cff4d9..85e680b51fbf 100644 --- a/packages/beacon-node/src/chain/blocks/index.ts +++ b/packages/beacon-node/src/chain/blocks/index.ts @@ -9,29 +9,29 @@ import type {BeaconChain} from "../chain.js"; import {verifyBlocksInEpoch} from "./verifyBlock.js"; import {importBlock} from "./importBlock.js"; import {assertLinearChainSegment} from "./utils/chainSegment.js"; -import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js"; +import {BlockInput, FullyVerifiedBlock, ImportBlockOpts} from "./types.js"; import {verifyBlocksSanityChecks} from "./verifyBlocksSanityChecks.js"; export {ImportBlockOpts} from "./types.js"; -const QUEUE_MAX_LENGHT = 256; +const QUEUE_MAX_LENGTH = 256; /** * BlockProcessor processes block jobs in a queued fashion, one after the other. */ export class BlockProcessor { - readonly jobQueue: JobItemQueue<[allForks.SignedBeaconBlock[], ImportBlockOpts], void>; + readonly jobQueue: JobItemQueue<[BlockInput[], ImportBlockOpts], void>; constructor(chain: BeaconChain, metrics: IMetrics | null, opts: BlockProcessOpts, signal: AbortSignal) { - this.jobQueue = new JobItemQueue<[allForks.SignedBeaconBlock[], ImportBlockOpts], void>( + this.jobQueue = new JobItemQueue<[BlockInput[], ImportBlockOpts], void>( (job, importOpts) => { return processBlocks.call(chain, job, {...opts, ...importOpts}); }, - {maxLength: QUEUE_MAX_LENGHT, signal}, + {maxLength: QUEUE_MAX_LENGTH, signal}, metrics?.blockProcessorQueue ?? undefined ); } - async processBlocksJob(job: allForks.SignedBeaconBlock[], opts: ImportBlockOpts = {}): Promise { + async processBlocksJob(job: BlockInput[], opts: ImportBlockOpts = {}): Promise { await this.jobQueue.push(job, opts); } } @@ -48,7 +48,7 @@ export class BlockProcessor { */ export async function processBlocks( this: BeaconChain, - blocks: allForks.SignedBeaconBlock[], + blocks: BlockInput[], opts: BlockProcessOpts & ImportBlockOpts ): Promise { if (blocks.length === 0) { @@ -87,7 +87,7 @@ export async function processBlocks( const {executionStatuses} = segmentExecStatus; const fullyVerifiedBlocks = relevantBlocks.map( (block, i): FullyVerifiedBlock => ({ - block, + blockInput: block, postState: postStates[i], parentBlockSlot: parentSlots[i], executionStatus: executionStatuses[i], @@ -104,7 +104,7 @@ export async function processBlocks( } } catch (e) { // above functions should only throw BlockError - const err = getBlockError(e, blocks[0]); + const err = getBlockError(e, blocks[0].block); // TODO: De-duplicate with logic above // ChainEvent.errorBlock diff --git a/packages/beacon-node/src/chain/blocks/types.ts b/packages/beacon-node/src/chain/blocks/types.ts index 024c1ea5166e..2252fb5c5cc2 100644 --- a/packages/beacon-node/src/chain/blocks/types.ts +++ b/packages/beacon-node/src/chain/blocks/types.ts @@ -1,6 +1,60 @@ -import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; import {MaybeValidExecutionStatus} from "@lodestar/fork-choice"; -import {allForks, Slot} from "@lodestar/types"; +import {allForks, eip4844, Slot} from "@lodestar/types"; +import {ForkSeq} from "@lodestar/params"; +import {IChainForkConfig} from "@lodestar/config"; + +export enum BlockInputType { + preEIP4844 = "preEIP4844", + postEIP4844 = "postEIP4844", + postEIP4844OldBlobs = "postEIP4844OldBlobs", +} + +export type BlockInput = + | {type: BlockInputType.preEIP4844; block: allForks.SignedBeaconBlock} + | {type: BlockInputType.postEIP4844; block: allForks.SignedBeaconBlock; blobs: eip4844.BlobsSidecar} + | {type: BlockInputType.postEIP4844OldBlobs; block: allForks.SignedBeaconBlock}; + +export function blockRequiresBlobs(config: IChainForkConfig, blockSlot: Slot, clockSlot: Slot): boolean { + return ( + config.getForkSeq(blockSlot) >= ForkSeq.eip4844 && + // Only request blobs if they are recent enough + computeEpochAtSlot(blockSlot) >= computeEpochAtSlot(clockSlot) - config.MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS + ); +} + +export const getBlockInput = { + preEIP4844(config: IChainForkConfig, block: allForks.SignedBeaconBlock): BlockInput { + if (config.getForkSeq(block.message.slot) >= ForkSeq.eip4844) { + throw Error(`Post EIP4844 block slot ${block.message.slot}`); + } + return { + type: BlockInputType.preEIP4844, + block, + }; + }, + + postEIP4844(config: IChainForkConfig, block: allForks.SignedBeaconBlock, blobs: eip4844.BlobsSidecar): BlockInput { + if (config.getForkSeq(block.message.slot) < ForkSeq.eip4844) { + throw Error(`Pre EIP4844 block slot ${block.message.slot}`); + } + return { + type: BlockInputType.postEIP4844, + block, + blobs, + }; + }, + + postEIP4844OldBlobs(config: IChainForkConfig, block: allForks.SignedBeaconBlock): BlockInput { + if (config.getForkSeq(block.message.slot) < ForkSeq.eip4844) { + throw Error(`Pre EIP4844 block slot ${block.message.slot}`); + } + return { + type: BlockInputType.postEIP4844OldBlobs, + block, + }; + }, +}; export type ImportBlockOpts = { /** @@ -35,6 +89,8 @@ export type ImportBlockOpts = { * Metadata: `true` if all the signatures including the proposer signature have been verified */ validSignatures?: boolean; + /** Set to true if already run `validateBlobsSidecar()` sucessfully on the blobs */ + validBlobsSidecar?: boolean; /** Seen timestamp seconds */ seenTimestampSec?: number; }; @@ -43,7 +99,7 @@ export type ImportBlockOpts = { * A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and ready to import */ export type FullyVerifiedBlock = { - block: allForks.SignedBeaconBlock; + blockInput: BlockInput; postState: CachedBeaconStateAllForks; parentBlockSlot: Slot; proposerBalanceDelta: number; diff --git a/packages/beacon-node/src/chain/blocks/utils/chainSegment.ts b/packages/beacon-node/src/chain/blocks/utils/chainSegment.ts index b2609edb3f50..e4da1e52c8da 100644 --- a/packages/beacon-node/src/chain/blocks/utils/chainSegment.ts +++ b/packages/beacon-node/src/chain/blocks/utils/chainSegment.ts @@ -1,28 +1,28 @@ import {IChainForkConfig} from "@lodestar/config"; -import {allForks, ssz} from "@lodestar/types"; +import {ssz} from "@lodestar/types"; import {BlockError, BlockErrorCode} from "../../errors/index.js"; +import {BlockInput} from "../types.js"; /** * Assert this chain segment of blocks is linear with slot numbers and hashes */ -export function assertLinearChainSegment(config: IChainForkConfig, blocks: allForks.SignedBeaconBlock[]): void { - for (const [i, block] of blocks.entries()) { - const child = blocks[i + 1]; - if (child !== undefined) { - // If this block has a child in this chain segment, ensure that its parent root matches - // the root of this block. - if ( - !ssz.Root.equals( - config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message), - child.message.parentRoot - ) - ) { - throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}); - } - // Ensure that the slots are strictly increasing throughout the chain segment. - if (child.message.slot <= block.message.slot) { - throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_SLOTS}); - } +export function assertLinearChainSegment(config: IChainForkConfig, blocks: BlockInput[]): void { + for (let i = 0; i < blocks.length - 1; i++) { + const block = blocks[i].block; + const child = blocks[i + 1].block; + // If this block has a child in this chain segment, ensure that its parent root matches + // the root of this block. + if ( + !ssz.Root.equals( + config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message), + child.message.parentRoot + ) + ) { + throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}); + } + // Ensure that the slots are strictly increasing throughout the chain segment. + if (child.message.slot <= block.message.slot) { + throw new BlockError(block, {code: BlockErrorCode.NON_LINEAR_SLOTS}); } } } diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 24c6fef10d17..9e4f16a22783 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -1,5 +1,5 @@ import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; -import {allForks, bellatrix} from "@lodestar/types"; +import {bellatrix} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; import {toHexString} from "@chainsafe/ssz"; import {ProtoBlock} from "@lodestar/fork-choice"; @@ -9,7 +9,7 @@ import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockProcessOpts} from "../options.js"; import {RegenCaller} from "../regen/index.js"; import type {BeaconChain} from "../chain.js"; -import {ImportBlockOpts} from "./types.js"; +import {BlockInput, ImportBlockOpts} from "./types.js"; import {POS_PANDA_MERGE_TRANSITION_BANNER} from "./utils/pandaMergeTransitionBanner.js"; import {CAPELLA_OWL_BANNER} from "./utils/ownBanner.js"; import {verifyBlocksStateTransitionOnly} from "./verifyBlocksStateTransitionOnly.js"; @@ -30,13 +30,14 @@ import {verifyBlocksExecutionPayload, SegmentExecStatus} from "./verifyBlocksExe export async function verifyBlocksInEpoch( this: BeaconChain, parentBlock: ProtoBlock, - blocks: allForks.SignedBeaconBlock[], + blocksImport: BlockInput[], opts: BlockProcessOpts & ImportBlockOpts ): Promise<{ postStates: CachedBeaconStateAllForks[]; proposerBalanceDeltas: number[]; segmentExecStatus: SegmentExecStatus; }> { + const blocks = blocksImport.map(({block}) => block); if (blocks.length === 0) { throw Error("Empty partiallyVerifiedBlocks"); } @@ -69,7 +70,7 @@ export async function verifyBlocksInEpoch( const [{postStates, proposerBalanceDeltas}, , segmentExecStatus] = await Promise.all([ // Run state transition only // TODO: Ensure it yields to allow flushing to workers and engine API - verifyBlocksStateTransitionOnly(preState0, blocks, this.metrics, abortController.signal, opts), + verifyBlocksStateTransitionOnly(preState0, blocksImport, this.metrics, abortController.signal, opts), // All signatures at once verifyBlocksSignatures(this.bls, preState0, blocks, opts), diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts index 1203845532e3..1d803d1b4186 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts @@ -1,11 +1,11 @@ import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {IChainForkConfig} from "@lodestar/config"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; -import {allForks, Slot} from "@lodestar/types"; +import {Slot} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; import {IBeaconClock} from "../clock/interface.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; -import {ImportBlockOpts} from "./types.js"; +import {BlockInput, ImportBlockOpts} from "./types.js"; /** * Verifies some early cheap sanity checks on the block before running the full state transition. @@ -21,18 +21,19 @@ import {ImportBlockOpts} from "./types.js"; */ export function verifyBlocksSanityChecks( chain: {forkChoice: IForkChoice; clock: IBeaconClock; config: IChainForkConfig}, - blocks: allForks.SignedBeaconBlock[], + blocks: BlockInput[], opts: ImportBlockOpts -): {relevantBlocks: allForks.SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { +): {relevantBlocks: BlockInput[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { if (blocks.length === 0) { throw Error("Empty partiallyVerifiedBlocks"); } - const relevantBlocks: allForks.SignedBeaconBlock[] = []; + const relevantBlocks: BlockInput[] = []; const parentSlots: Slot[] = []; let parentBlock: ProtoBlock | null = null; - for (const block of blocks) { + for (const blockInput of blocks) { + const {block} = blockInput; const blockSlot = block.message.slot; // Not genesis block @@ -59,7 +60,7 @@ export function verifyBlocksSanityChecks( let parentBlockSlot: Slot; if (relevantBlocks.length > 0) { - parentBlockSlot = relevantBlocks[relevantBlocks.length - 1].message.slot; + parentBlockSlot = relevantBlocks[relevantBlocks.length - 1].block.message.slot; } else { // When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice. const parentRoot = toHexString(block.message.parentRoot); @@ -92,7 +93,7 @@ export function verifyBlocksSanityChecks( } // Block is relevant - relevantBlocks.push(block); + relevantBlocks.push(blockInput); parentSlots.push(parentBlockSlot); } diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts index e1c6b2031df8..79f1bed2da8e 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts @@ -1,11 +1,10 @@ import {CachedBeaconStateAllForks, stateTransition} from "@lodestar/state-transition"; -import {allForks} from "@lodestar/types"; import {ErrorAborted, sleep} from "@lodestar/utils"; import {IMetrics} from "../../metrics/index.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockProcessOpts} from "../options.js"; import {byteArrayEquals} from "../../util/bytes.js"; -import {ImportBlockOpts} from "./types.js"; +import {BlockInput, ImportBlockOpts} from "./types.js"; /** * Verifies 1 or more blocks are fully valid running the full state transition; from a linear sequence of blocks. @@ -17,7 +16,7 @@ import {ImportBlockOpts} from "./types.js"; */ export async function verifyBlocksStateTransitionOnly( preState0: CachedBeaconStateAllForks, - blocks: allForks.SignedBeaconBlock[], + blocks: BlockInput[], metrics: IMetrics | null, signal: AbortSignal, opts: BlockProcessOpts & ImportBlockOpts @@ -27,7 +26,7 @@ export async function verifyBlocksStateTransitionOnly( for (let i = 0; i < blocks.length; i++) { const {validProposerSignature, validSignatures} = opts; - const block = blocks[i]; + const {block} = blocks[i]; const preState = i === 0 ? preState0 : postStates[i - 1]; // STFN - per_slot_processing() + per_block_processing() diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 8dfa5d77971e..0ddaa893afd6 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -62,6 +62,7 @@ import {CheckpointBalancesCache} from "./balancesCache.js"; import {AssembledBlockType, BlockType} from "./produceBlock/index.js"; import {BlockAttributes, produceBlockBody} from "./produceBlock/produceBlockBody.js"; import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; +import {BlockInput} from "./blocks/types.js"; export class BeaconChain implements IBeaconChain { readonly genesisTime: UintNum64; @@ -379,11 +380,11 @@ export class BeaconChain implements IBeaconChain { return block; } - async processBlock(block: allForks.SignedBeaconBlock, opts?: ImportBlockOpts): Promise { + async processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise { return await this.blockProcessor.processBlocksJob([block], opts); } - async processChainSegment(blocks: allForks.SignedBeaconBlock[], opts?: ImportBlockOpts): Promise { + async processChainSegment(blocks: BlockInput[], opts?: ImportBlockOpts): Promise { return await this.blockProcessor.processBlocksJob(blocks, opts); } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index c30dfb06c790..b6519a171ce2 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -22,7 +22,7 @@ import { import {AttestationPool, OpPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools/index.js"; import {LightClientServer} from "./lightClient/index.js"; import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool.js"; -import {ImportBlockOpts} from "./blocks/types.js"; +import {BlockInput, ImportBlockOpts} from "./blocks/types.js"; import {ReprocessController} from "./reprocess.js"; import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; import {BeaconProposerCache, ProposerPreparationData} from "./beaconProposerCache.js"; @@ -112,9 +112,9 @@ export interface IBeaconChain { produceBlindedBlock(blockAttributes: BlockAttributes): Promise; /** Process a block until complete */ - processBlock(block: allForks.SignedBeaconBlock, opts?: ImportBlockOpts): Promise; + processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise; /** Process a chain of blocks until complete */ - processChainSegment(blocks: allForks.SignedBeaconBlock[], opts?: ImportBlockOpts): Promise; + processChainSegment(blocks: BlockInput[], opts?: ImportBlockOpts): Promise; getStatus(): phase0.Status; diff --git a/packages/beacon-node/src/network/events.ts b/packages/beacon-node/src/network/events.ts index 4cc23bcab1b2..2973ca60a4e8 100644 --- a/packages/beacon-node/src/network/events.ts +++ b/packages/beacon-node/src/network/events.ts @@ -1,7 +1,8 @@ import {EventEmitter} from "events"; import {PeerId} from "@libp2p/interface-peer-id"; import StrictEventEmitter from "strict-event-emitter-types"; -import {allForks, phase0} from "@lodestar/types"; +import {phase0} from "@lodestar/types"; +import {BlockInput} from "../chain/blocks/types.js"; import {RequestTypedContainer} from "./reqresp/ReqRespBeaconNode.js"; export enum NetworkEvent { @@ -19,7 +20,7 @@ export type NetworkEvents = { [NetworkEvent.peerConnected]: (peer: PeerId, status: phase0.Status) => void; [NetworkEvent.peerDisconnected]: (peer: PeerId) => void; [NetworkEvent.reqRespRequest]: (request: RequestTypedContainer, peer: PeerId) => void; - [NetworkEvent.unknownBlockParent]: (signedBlock: allForks.SignedBeaconBlock, peerIdStr: string) => void; + [NetworkEvent.unknownBlockParent]: (blockInput: BlockInput, peerIdStr: string) => void; }; export type INetworkEventBus = StrictEventEmitter; diff --git a/packages/beacon-node/src/network/gossip/handlers/index.ts b/packages/beacon-node/src/network/gossip/handlers/index.ts index 7b55b32c1e7e..d5012a72f077 100644 --- a/packages/beacon-node/src/network/gossip/handlers/index.ts +++ b/packages/beacon-node/src/network/gossip/handlers/index.ts @@ -31,6 +31,7 @@ import {NetworkEvent} from "../../events.js"; import {PeerAction} from "../../peers/index.js"; import {validateLightClientFinalityUpdate} from "../../../chain/validation/lightClientFinalityUpdate.js"; import {validateLightClientOptimisticUpdate} from "../../../chain/validation/lightClientOptimisticUpdate.js"; +import {getBlockInput} from "../../../chain/blocks/types.js"; /** * Gossip handler options as part of network options @@ -88,13 +89,16 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH delaySec, }); + // TODO EIP-4844: Will throw an error for blocks post EIP-4844 + const blockInput = getBlockInput.preEIP4844(config, signedBlock); + try { await validateGossipBlock(config, chain, signedBlock, topic.fork); } catch (e) { if (e instanceof BlockGossipError) { if (e instanceof BlockGossipError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { logger.debug("Gossip block has error", {slot, root: blockHex, code: e.type.code}); - network.events.emit(NetworkEvent.unknownBlockParent, signedBlock, peerIdStr); + network.events.emit(NetworkEvent.unknownBlockParent, blockInput, peerIdStr); } } @@ -117,7 +121,7 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH // otherwise we can't utilize bls thread pool capacity and Gossip Job Wait Time can't be kept low consistently. // See https://github.com/ChainSafe/lodestar/issues/3792 chain - .processBlock(signedBlock, {validProposerSignature: true, blsVerifyOnMainThread: true}) + .processBlock(blockInput, {validProposerSignature: true, blsVerifyOnMainThread: true}) .then(() => { // Returns the delay between the start of `block.slot` and `current time` const delaySec = chain.clock.secFromSlot(slot); diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index 2f41efc94de0..a357b3a9403b 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -2,6 +2,8 @@ import {Connection} from "@libp2p/interface-connection"; import {Multiaddr} from "@multiformats/multiaddr"; import {PeerId} from "@libp2p/interface-peer-id"; import {Discv5, ENR} from "@chainsafe/discv5"; +import {phase0} from "@lodestar/types"; +import {BlockInput} from "../chain/blocks/types.js"; import {INetworkEventBus} from "./events.js"; import {Eth2Gossipsub} from "./gossip/index.js"; import {MetadataController} from "./metadata.js"; @@ -29,6 +31,10 @@ export interface INetwork { getConnectionsByPeer(): Map; getConnectedPeers(): PeerId[]; hasSomeConnectedPeer(): boolean; + + beaconBlocksMaybeBlobsByRange(peerId: PeerId, request: phase0.BeaconBlocksByRangeRequest): Promise; + beaconBlocksMaybeBlobsByRoot(peerId: PeerId, request: phase0.BeaconBlocksByRootRequest): Promise; + /** Subscribe, search peers, join long-lived attnets */ prepareBeaconCommitteeSubnet(subscriptions: CommitteeSubscription[]): void; /** Subscribe, search peers, join long-lived syncnets */ diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 12577336a7f1..ab0273f3793a 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -8,9 +8,10 @@ import {ILogger, sleep} from "@lodestar/utils"; import {ATTESTATION_SUBNET_COUNT, ForkName, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {Discv5, ENR} from "@chainsafe/discv5"; import {computeEpochAtSlot, computeTimeAtSlot} from "@lodestar/state-transition"; -import {altair, Epoch} from "@lodestar/types"; +import {altair, Epoch, phase0} from "@lodestar/types"; import {IMetrics} from "../metrics/index.js"; import {ChainEvent, IBeaconChain, IBeaconClock} from "../chain/index.js"; +import {BlockInput, getBlockInput} from "../chain/blocks/types.js"; import {INetworkOptions} from "./options.js"; import {INetwork} from "./interface.js"; import {IReqRespBeaconNode, ReqRespBeaconNode, ReqRespHandlers} from "./reqresp/ReqRespBeaconNode.js"; @@ -195,6 +196,21 @@ export class Network implements INetwork { return this.peerManager.hasSomeConnectedPeer(); } + async beaconBlocksMaybeBlobsByRange( + peerId: PeerId, + request: phase0.BeaconBlocksByRangeRequest + ): Promise { + // TODO EIP-4844: Will throw an error for blocks post EIP-4844 + const blocks = await this.reqResp.beaconBlocksByRange(peerId, request); + return blocks.map((block) => getBlockInput.preEIP4844(this.config, block)); + } + + async beaconBlocksMaybeBlobsByRoot(peerId: PeerId, request: phase0.BeaconBlocksByRootRequest): Promise { + // TODO EIP-4844: Will throw an error for blocks post EIP-4844 + const blocks = await this.reqResp.beaconBlocksByRoot(peerId, request); + return blocks.map((block) => getBlockInput.preEIP4844(this.config, block)); + } + /** * Request att subnets up `toSlot`. Network will ensure to mantain some peers for each */ diff --git a/packages/beacon-node/src/sync/interface.ts b/packages/beacon-node/src/sync/interface.ts index 8debff8982ad..ab1e5840c111 100644 --- a/packages/beacon-node/src/sync/interface.ts +++ b/packages/beacon-node/src/sync/interface.ts @@ -1,7 +1,8 @@ import {ILogger} from "@lodestar/utils"; -import {allForks, RootHex, Slot, phase0} from "@lodestar/types"; +import {RootHex, Slot, phase0} from "@lodestar/types"; import {IBeaconConfig} from "@lodestar/config"; import {routes} from "@lodestar/api"; +import {BlockInput} from "../chain/blocks/types.js"; import {INetwork} from "../network/index.js"; import {IBeaconChain} from "../chain/index.js"; import {IMetrics} from "../metrics/index.js"; @@ -61,7 +62,7 @@ export interface ISyncModules { export type PendingBlock = { blockRootHex: RootHex; parentBlockRootHex: RootHex; - signedBlock: allForks.SignedBeaconBlock; + blockInput: BlockInput; peerIdStrs: Set; status: PendingBlockStatus; downloadAttempts: number; diff --git a/packages/beacon-node/src/sync/range/batch.ts b/packages/beacon-node/src/sync/range/batch.ts index 6061574546c5..c3c9e65c28fd 100644 --- a/packages/beacon-node/src/sync/range/batch.ts +++ b/packages/beacon-node/src/sync/range/batch.ts @@ -1,8 +1,9 @@ import {PeerId} from "@libp2p/interface-peer-id"; -import {allForks, Epoch, phase0, RootHex} from "@lodestar/types"; +import {Epoch, phase0, RootHex} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {LodestarError} from "@lodestar/utils"; import {MAX_BATCH_DOWNLOAD_ATTEMPTS, MAX_BATCH_PROCESSING_ATTEMPTS} from "../constants.js"; +import {BlockInput} from "../../chain/blocks/types.js"; import {BlockError, BlockErrorCode} from "../../chain/errors/index.js"; import {getBatchSlotRange, hashBlocks} from "./utils/index.js"; @@ -38,7 +39,7 @@ export type Attempt = { export type BatchState = | {status: BatchStatus.AwaitingDownload} | {status: BatchStatus.Downloading; peer: PeerId} - | {status: BatchStatus.AwaitingProcessing; peer: PeerId; blocks: allForks.SignedBeaconBlock[]} + | {status: BatchStatus.AwaitingProcessing; peer: PeerId; blocks: BlockInput[]} | {status: BatchStatus.Processing; attempt: Attempt} | {status: BatchStatus.AwaitingValidation; attempt: Attempt}; @@ -109,7 +110,7 @@ export class Batch { /** * Downloading -> AwaitingProcessing */ - downloadingSuccess(blocks: allForks.SignedBeaconBlock[]): void { + downloadingSuccess(blocks: BlockInput[]): void { if (this.state.status !== BatchStatus.Downloading) { throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading)); } @@ -136,7 +137,7 @@ export class Batch { /** * AwaitingProcessing -> Processing */ - startProcessing(): allForks.SignedBeaconBlock[] { + startProcessing(): BlockInput[] { if (this.state.status !== BatchStatus.AwaitingProcessing) { throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing)); } diff --git a/packages/beacon-node/src/sync/range/chain.ts b/packages/beacon-node/src/sync/range/chain.ts index bd199e892969..297175be4d7f 100644 --- a/packages/beacon-node/src/sync/range/chain.ts +++ b/packages/beacon-node/src/sync/range/chain.ts @@ -1,8 +1,9 @@ import {PeerId} from "@libp2p/interface-peer-id"; -import {Epoch, Root, Slot, phase0, allForks} from "@lodestar/types"; +import {Epoch, Root, Slot, phase0} from "@lodestar/types"; import {ErrorAborted, ILogger} from "@lodestar/utils"; import {IChainForkConfig} from "@lodestar/config"; import {toHexString} from "@chainsafe/ssz"; +import {BlockInput} from "../../chain/blocks/types.js"; import {PeerAction} from "../../network/index.js"; import {ItTrigger} from "../../util/itTrigger.js"; import {PeerMap} from "../../util/peerMap.js"; @@ -32,12 +33,9 @@ export type SyncChainFns = { * Must return if ALL blocks are processed successfully * If SOME blocks are processed must throw BlockProcessorError() */ - processChainSegment: (blocks: allForks.SignedBeaconBlock[], syncType: RangeSyncType) => Promise; + processChainSegment: (blocks: BlockInput[], syncType: RangeSyncType) => Promise; /** Must download blocks, and validate their range */ - downloadBeaconBlocksByRange: ( - peer: PeerId, - request: phase0.BeaconBlocksByRangeRequest - ) => Promise; + downloadBeaconBlocksByRange: (peer: PeerId, request: phase0.BeaconBlocksByRangeRequest) => Promise; /** Report peer for negative actions. Decouples from the full network instance */ reportPeer: (peer: PeerId, action: PeerAction, actionName: string) => void; /** Hook called when Chain state completes */ diff --git a/packages/beacon-node/src/sync/range/range.ts b/packages/beacon-node/src/sync/range/range.ts index dbe9d0dcdfb7..14c3feaf44e2 100644 --- a/packages/beacon-node/src/sync/range/range.ts +++ b/packages/beacon-node/src/sync/range/range.ts @@ -198,7 +198,7 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) { /** Convenience method for `SyncChain` */ private downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (peerId, request) => { - return await this.network.reqResp.beaconBlocksByRange(peerId, request); + return await this.network.beaconBlocksMaybeBlobsByRange(peerId, request); }; /** Convenience method for `SyncChain` */ diff --git a/packages/beacon-node/src/sync/range/utils/hashBlocks.ts b/packages/beacon-node/src/sync/range/utils/hashBlocks.ts index ef7461056ec8..baa7a4afe9a6 100644 --- a/packages/beacon-node/src/sync/range/utils/hashBlocks.ts +++ b/packages/beacon-node/src/sync/range/utils/hashBlocks.ts @@ -1,19 +1,22 @@ -import {allForks, RootHex} from "@lodestar/types"; +import {RootHex} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {toHex} from "@lodestar/utils"; +import {BlockInput} from "../../../chain/blocks/types.js"; /** * String to uniquely identify block segments. Used for peer scoring and to compare if batches are equivalent. */ -export function hashBlocks(blocks: allForks.SignedBeaconBlock[], config: IChainForkConfig): RootHex { +export function hashBlocks(blocks: BlockInput[], config: IChainForkConfig): RootHex { switch (blocks.length) { case 0: return "0x"; - case 1: - return toHex(config.getForkTypes(blocks[0].message.slot).SignedBeaconBlock.hashTreeRoot(blocks[0])); + case 1: { + const block0 = blocks[0].block; + return toHex(config.getForkTypes(block0.message.slot).SignedBeaconBlock.hashTreeRoot(block0)); + } default: { - const block0 = blocks[0]; - const blockN = blocks[blocks.length - 1]; + const block0 = blocks[0].block; + const blockN = blocks[blocks.length - 1].block; return ( toHex(config.getForkTypes(block0.message.slot).SignedBeaconBlock.hashTreeRoot(block0)) + toHex(config.getForkTypes(blockN.message.slot).SignedBeaconBlock.hashTreeRoot(blockN)) diff --git a/packages/beacon-node/src/sync/unknownBlock.ts b/packages/beacon-node/src/sync/unknownBlock.ts index 054256a7de96..a6d032506058 100644 --- a/packages/beacon-node/src/sync/unknownBlock.ts +++ b/packages/beacon-node/src/sync/unknownBlock.ts @@ -2,10 +2,11 @@ import {PeerId} from "@libp2p/interface-peer-id"; import {peerIdFromString} from "@libp2p/peer-id"; import {IChainForkConfig} from "@lodestar/config"; import {ILogger, pruneSetToMax} from "@lodestar/utils"; -import {allForks, Root, RootHex} from "@lodestar/types"; +import {Root, RootHex} from "@lodestar/types"; import {fromHexString, toHexString} from "@chainsafe/ssz"; import {INetwork, NetworkEvent, PeerAction} from "../network/index.js"; import {IBeaconChain} from "../chain/index.js"; +import {BlockInput} from "../chain/blocks/types.js"; import {IMetrics} from "../metrics/index.js"; import {shuffle} from "../util/shuffle.js"; import {byteArrayEquals} from "../util/bytes.js"; @@ -61,9 +62,9 @@ export class UnknownBlockSync { /** * Process an unknownBlockParent event and register the block in `pendingBlocks` Map. */ - private onUnknownBlock = (signedBlock: allForks.SignedBeaconBlock, peerIdStr: string): void => { + private onUnknownBlock = (blockInput: BlockInput, peerIdStr: string): void => { try { - this.addToPendingBlocks(signedBlock, peerIdStr); + this.addToPendingBlocks(blockInput, peerIdStr); this.triggerUnknownBlockSearch(); this.metrics?.syncUnknownBlock.requests.inc(); } catch (e) { @@ -71,8 +72,8 @@ export class UnknownBlockSync { } }; - private addToPendingBlocks(signedBlock: allForks.SignedBeaconBlock, peerIdStr: string): PendingBlock { - const block = signedBlock.message; + private addToPendingBlocks(blockInput: BlockInput, peerIdStr: string): PendingBlock { + const block = blockInput.block.message; const blockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block); const blockRootHex = toHexString(blockRoot); const parentBlockRootHex = toHexString(block.parentRoot); @@ -82,7 +83,7 @@ export class UnknownBlockSync { pendingBlock = { blockRootHex, parentBlockRootHex, - signedBlock, + blockInput, peerIdStrs: new Set(), status: PendingBlockStatus.pending, downloadAttempts: 0, @@ -135,12 +136,12 @@ export class UnknownBlockSync { else this.metrics?.syncUnknownBlock.downloadedBlocksSuccess.inc(); if (!res.err) { - const {signedBlock, peerIdStr} = res.result; - const parentSlot = signedBlock.message.slot; + const {blockInput, peerIdStr} = res.result; + const parentSlot = blockInput.block.message.slot; const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot; - if (this.chain.forkChoice.hasBlock(signedBlock.message.parentRoot)) { + if (this.chain.forkChoice.hasBlock(blockInput.block.message.parentRoot)) { // Bingo! Process block. Add to pending blocks anyway for recycle the cache that prevents duplicate processing - this.processBlock(this.addToPendingBlocks(signedBlock, peerIdStr)).catch((e) => { + this.processBlock(this.addToPendingBlocks(blockInput, peerIdStr)).catch((e) => { this.logger.error("Unexpect error - processBlock", {}, e); }); } else if (parentSlot <= finalizedSlot) { @@ -149,14 +150,15 @@ export class UnknownBlockSync { // 0 - 1 - ... - n - finalizedSlot // \ // parent 1 - parent 2 - ... - unknownParent block + const parentRoot = this.config.getForkTypes(parentSlot).BeaconBlock.hashTreeRoot(blockInput.block.message); this.logger.error("Downloaded block parent is before finalized slot", { finalizedSlot, parentSlot, - parentRoot: toHexString(this.config.getForkTypes(parentSlot).BeaconBlock.hashTreeRoot(signedBlock.message)), + parentRoot: toHexString(parentRoot), }); this.removeAndDownscoreAllDescendants(block); } else { - this.onUnknownBlock(signedBlock, peerIdStr); + this.onUnknownBlock(blockInput, peerIdStr); } } else { // parentSlot > finalizedSlot, continue downloading parent of parent @@ -190,7 +192,7 @@ export class UnknownBlockSync { // otherwise we can't utilize bls thread pool capacity and Gossip Job Wait Time can't be kept low consistently. // See https://github.com/ChainSafe/lodestar/issues/3792 const res = await wrapError( - this.chain.processBlock(pendingBlock.signedBlock, {ignoreIfKnown: true, blsVerifyOnMainThread: true}) + this.chain.processBlock(pendingBlock.blockInput, {ignoreIfKnown: true, blsVerifyOnMainThread: true}) ); pendingBlock.status = PendingBlockStatus.pending; @@ -207,7 +209,7 @@ export class UnknownBlockSync { }); } } else { - const errorData = {root: pendingBlock.blockRootHex, slot: pendingBlock.signedBlock.message.slot}; + const errorData = {root: pendingBlock.blockRootHex, slot: pendingBlock.blockInput.block.message.slot}; if (res.err instanceof BlockError) { switch (res.err.type.code) { // This cases are already handled with `{ignoreIfKnown: true}` @@ -250,7 +252,7 @@ export class UnknownBlockSync { private async fetchUnknownBlockRoot( blockRoot: Root, connectedPeers: PeerId[] - ): Promise<{signedBlock: allForks.SignedBeaconBlock; peerIdStr: string}> { + ): Promise<{blockInput: BlockInput; peerIdStr: string}> { const shuffledPeers = shuffle(connectedPeers); const blockRootHex = toHexString(blockRoot); @@ -258,21 +260,22 @@ export class UnknownBlockSync { for (let i = 0; i < MAX_ATTEMPTS_PER_BLOCK; i++) { const peer = shuffledPeers[i % shuffledPeers.length]; try { - const [signedBlock] = await this.network.reqResp.beaconBlocksByRoot(peer, [blockRoot]); + // TODO EIP-4844: Use + const [blockInput] = await this.network.beaconBlocksMaybeBlobsByRoot(peer, [blockRoot]); // Peer does not have the block, try with next peer - if (signedBlock === undefined) { + if (blockInput === undefined) { continue; } // Verify block root is correct - const block = signedBlock.message; + const block = blockInput.block.message; const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block); if (!byteArrayEquals(receivedBlockRoot, blockRoot)) { throw Error(`Wrong block received by peer, expected ${toHexString(receivedBlockRoot)} got ${blockRootHex}`); } - return {signedBlock, peerIdStr: peer.toString()}; + return {blockInput, peerIdStr: peer.toString()}; } catch (e) { this.logger.debug( "Error fetching UnknownBlockRoot", @@ -305,7 +308,7 @@ export class UnknownBlockSync { this.knownBadBlocks.add(block.blockRootHex); this.logger.error("Banning unknown parent block", { root: block.blockRootHex, - slot: block.signedBlock.message.slot, + slot: block.blockInput.block.message.slot, }); for (const peerIdStr of block.peerIdStrs) { @@ -329,7 +332,7 @@ export class UnknownBlockSync { this.pendingBlocks.delete(block.blockRootHex); this.logger.error("Removing unknown parent block", { root: block.blockRootHex, - slot: block.signedBlock.message.slot, + slot: block.blockInput.block.message.slot, }); } diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index 444db952ed75..bfc56472c701 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -16,6 +16,7 @@ import {Eth1ForBlockProductionDisabled} from "../../../src/eth1/index.js"; import {testLogger} from "../../utils/logger.js"; import {linspace} from "../../../src/util/numpy.js"; import {BeaconDb} from "../../../src/index.js"; +import {getBlockInput} from "../../../src/chain/blocks/types.js"; // Define this params in `packages/state-transition/test/perf/params.ts` // to trigger Github actions CI cache @@ -105,7 +106,9 @@ describe.skip("verify+import blocks - range sync perf test", () => { return chain; }, fn: async (chain) => { - await chain.processChainSegment(blocks.value, { + const blocksImport = blocks.value.map((block) => getBlockInput.preEIP4844(chain.config, block)); + + await chain.processChainSegment(blocksImport, { // Only skip importing attestations for finalized sync. For head sync attestation are valuable. // Importing attestations also triggers a head update, see https://github.com/ChainSafe/lodestar/issues/3804 // TODO: Review if this is okay, can we prevent some attacks by importing attestations? diff --git a/packages/beacon-node/test/spec/presets/fork_choice.ts b/packages/beacon-node/test/spec/presets/fork_choice.ts index df080ba72526..acc8ebe8b4c2 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.ts @@ -16,6 +16,7 @@ import {ExecutionEngineMock} from "../../../src/execution/index.js"; import {defaultChainOptions} from "../../../src/chain/options.js"; import {getStubbedBeaconDb} from "../../utils/mocks/db.js"; import {ClockStopped} from "../../utils/mocks/clock.js"; +import {getBlockInput} from "../../../src/chain/blocks/types.js"; import {ZERO_HASH_HEX} from "../../../src/constants/constants.js"; import {PowMergeBlock} from "../../../src/eth1/interface.js"; import {assertCorrectProgressiveBalances} from "../config.js"; @@ -137,8 +138,10 @@ export const forkChoiceTest: TestRunnerFn = (fork) => isValid, }); + const blockImport = getBlockInput.preEIP4844(config, signedBlock); + try { - await chain.processBlock(signedBlock, {seenTimestampSec: tickTime}); + await chain.processBlock(blockImport, {seenTimestampSec: tickTime}); if (!isValid) throw Error("Expect error since this is a negative test"); } catch (e) { if (isValid) throw e; diff --git a/packages/beacon-node/test/unit/api/impl/beacon/blocks/publishBlock.test.ts b/packages/beacon-node/test/unit/api/impl/beacon/blocks/publishBlock.test.ts deleted file mode 100644 index bf2ecb47f1fd..000000000000 --- a/packages/beacon-node/test/unit/api/impl/beacon/blocks/publishBlock.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {expect, use} from "chai"; -import chaiAsPromised from "chai-as-promised"; -import sinon, {SinonStubbedInstance} from "sinon"; -import {allForks} from "@lodestar/types"; -import {getBeaconBlockApi} from "../../../../../../src/api/impl/beacon/blocks/index.js"; -import {Eth2Gossipsub} from "../../../../../../src/network/gossip/index.js"; -import {generateEmptySignedBlock} from "../../../../../utils/block.js"; -import {BeaconSync} from "../../../../../../src/sync/index.js"; -import {setupApiImplTestServer, ApiImplTestModules} from "../../index.test.js"; - -use(chaiAsPromised); - -describe("api - beacon - publishBlock", function () { - let gossipStub: SinonStubbedInstance; - let block: allForks.SignedBeaconBlock; - let chainStub: ApiImplTestModules["chainStub"]; - let syncStub: SinonStubbedInstance; - let server: ApiImplTestModules; - - before(function () { - block = generateEmptySignedBlock(); - }); - - beforeEach(function () { - server = setupApiImplTestServer(); - gossipStub = sinon.createStubInstance(Eth2Gossipsub); - gossipStub.publishBeaconBlock = sinon.stub(); - server.networkStub.gossip = (gossipStub as unknown) as Eth2Gossipsub; - chainStub = server.chainStub; - syncStub = server.syncStub; - chainStub.processBlock.resolves(); - }); - - it("successful publish", async function () { - const blockApi = getBeaconBlockApi({ - chain: chainStub, - config: server.config, - db: server.dbStub, - network: server.networkStub, - metrics: null, - }); - - syncStub.isSynced.returns(true); - await expect(blockApi.publishBlock(block)).to.be.fulfilled; - expect(chainStub.processBlock).to.be.calledOnceWith(block); - expect(gossipStub.publishBeaconBlock).to.be.calledOnceWith(block); - }); -}); diff --git a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts index c25edca2d995..1d0b40ba2ae1 100644 --- a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts +++ b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts @@ -7,11 +7,12 @@ import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {toHex} from "@lodestar/utils"; import {IChainForkConfig} from "@lodestar/config"; import {allForks, Slot, ssz} from "@lodestar/types"; -import {verifyBlocksSanityChecks} from "../../../../src/chain/blocks/verifyBlocksSanityChecks.js"; +import {verifyBlocksSanityChecks as verifyBlocksImportSanityChecks} from "../../../../src/chain/blocks/verifyBlocksSanityChecks.js"; import {BlockErrorCode} from "../../../../src/chain/errors/index.js"; import {expectThrowsLodestarError} from "../../../utils/errors.js"; import {IBeaconClock} from "../../../../src/chain/index.js"; import {ClockStopped} from "../../../utils/mocks/clock.js"; +import {getBlockInput} from "../../../../src/chain/blocks/types.js"; describe("chain / blocks / verifyBlocksSanityChecks", function () { let forkChoice: SinonStubbedInstance; @@ -116,6 +117,26 @@ describe("chain / blocks / verifyBlocksSanityChecks", function () { }); }); +/** + * Wrap verifyBlocksSanityChecks to deal with SignedBeaconBlock instead of BlockImport + */ +function verifyBlocksSanityChecks( + modules: Parameters[0], + blocks: allForks.SignedBeaconBlock[], + opts: Parameters[2] +): {relevantBlocks: allForks.SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { + const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksImportSanityChecks( + modules, + blocks.map((block) => getBlockInput.preEIP4844(config, block)), + opts + ); + return { + relevantBlocks: relevantBlocks.map(({block}) => block), + parentSlots, + parentBlock, + }; +} + function getValidChain(count: number, initialSlot = 0): allForks.SignedBeaconBlock[] { const blocks: allForks.SignedBeaconBlock[] = []; diff --git a/packages/beacon-node/test/unit/sync/range/batch.test.ts b/packages/beacon-node/test/unit/sync/range/batch.test.ts index b60b00607b85..9fa12e19968f 100644 --- a/packages/beacon-node/test/unit/sync/range/batch.test.ts +++ b/packages/beacon-node/test/unit/sync/range/batch.test.ts @@ -6,12 +6,13 @@ import {generateEmptySignedBlock} from "../../../utils/block.js"; import {expectThrowsLodestarError} from "../../../utils/errors.js"; import {Batch, BatchStatus, BatchErrorCode, BatchError} from "../../../../src/sync/range/batch.js"; import {EPOCHS_PER_BATCH} from "../../../../src/sync/constants.js"; +import {getBlockInput} from "../../../../src/chain/blocks/types.js"; describe("sync / range / batch", async () => { // Common mock data const startEpoch = 0; const peer = await createSecp256k1PeerId(); - const blocksDownloaded = [generateEmptySignedBlock()]; + const blocksDownloaded = [getBlockInput.preEIP4844(config, generateEmptySignedBlock())]; it("Should return correct blockByRangeRequest", () => { const batch = new Batch(startEpoch, config); diff --git a/packages/beacon-node/test/unit/sync/range/chain.test.ts b/packages/beacon-node/test/unit/sync/range/chain.test.ts index 6247030e4f2e..03b675f9a7d5 100644 --- a/packages/beacon-node/test/unit/sync/range/chain.test.ts +++ b/packages/beacon-node/test/unit/sync/range/chain.test.ts @@ -9,6 +9,7 @@ import {RangeSyncType} from "../../../../src/sync/utils/remoteSyncType.js"; import {ZERO_HASH} from "../../../../src/constants/index.js"; import {testLogger} from "../../../utils/logger.js"; import {getValidPeerId} from "../../../utils/peer.js"; +import {BlockInput, getBlockInput} from "../../../../src/chain/blocks/types.js"; describe("sync / range / chain", () => { const testCases: { @@ -65,14 +66,14 @@ describe("sync / range / chain", () => { for (const {id, startEpoch, targetEpoch, badBlocks, skippedSlots} of testCases) { it(id, async () => { const processChainSegment: SyncChainFns["processChainSegment"] = async (blocks) => { - for (const block of blocks) { + for (const {block} of blocks) { if (block.signature === ACCEPT_BLOCK) continue; if (block.signature === REJECT_BLOCK) throw Error("REJECT_BLOCK"); } }; const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (peerId, request) => { - const blocks: phase0.SignedBeaconBlock[] = []; + const blocks: BlockInput[] = []; for (let i = request.startSlot; i < request.startSlot + request.count; i += request.step) { if (skippedSlots?.has(i)) { continue; // Skip @@ -81,10 +82,12 @@ describe("sync / range / chain", () => { // Only reject once to prevent an infinite loop const shouldReject = badBlocks?.has(i); if (shouldReject) badBlocks?.delete(i); - blocks.push({ - message: generateEmptyBlock(i), - signature: shouldReject ? REJECT_BLOCK : ACCEPT_BLOCK, - }); + blocks.push( + getBlockInput.preEIP4844(config, { + message: generateEmptyBlock(i), + signature: shouldReject ? REJECT_BLOCK : ACCEPT_BLOCK, + }) + ); } return blocks; }; @@ -118,12 +121,14 @@ describe("sync / range / chain", () => { // eslint-disable-next-line @typescript-eslint/no-empty-function const processChainSegment: SyncChainFns["processChainSegment"] = async () => {}; const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (peer, request) => { - const blocks: phase0.SignedBeaconBlock[] = []; + const blocks: BlockInput[] = []; for (let i = request.startSlot; i < request.startSlot + request.count; i += request.step) { - blocks.push({ - message: generateEmptyBlock(i), - signature: ACCEPT_BLOCK, - }); + blocks.push( + getBlockInput.preEIP4844(config, { + message: generateEmptyBlock(i), + signature: ACCEPT_BLOCK, + }) + ); } return blocks; }; @@ -164,7 +169,7 @@ describe("sync / range / chain", () => { function logSyncChainFns(logger: ILogger, fns: SyncChainFns): SyncChainFns { return { processChainSegment(blocks, syncType) { - logger.debug("mock processChainSegment", {blocks: blocks.map((b) => b.message.slot).join(",")}); + logger.debug("mock processChainSegment", {blocks: blocks.map((b) => b.block.message.slot).join(",")}); return fns.processChainSegment(blocks, syncType); }, downloadBeaconBlocksByRange(peer, request) { diff --git a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts index dd7455cc3c6f..cb405fd505ce 100644 --- a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts +++ b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts @@ -5,10 +5,11 @@ import {ssz} from "@lodestar/types"; import {notNullish, sleep} from "@lodestar/utils"; import {toHexString} from "@chainsafe/ssz"; import {IBeaconChain} from "../../../src/chain/index.js"; -import {INetwork, IReqRespBeaconNode, NetworkEvent, NetworkEventBus, PeerAction} from "../../../src/network/index.js"; +import {INetwork, NetworkEvent, NetworkEventBus, PeerAction} from "../../../src/network/index.js"; import {UnknownBlockSync} from "../../../src/sync/unknownBlock.js"; import {testLogger} from "../../utils/logger.js"; import {getValidPeerId} from "../../utils/peer.js"; +import {getBlockInput} from "../../../src/chain/blocks/types.js"; describe("sync / UnknownBlockSync", () => { const logger = testLogger(); @@ -44,20 +45,18 @@ describe("sync / UnknownBlockSync", () => { [blockRootHexB, blockB], ]); - const reqResp: Partial = { - beaconBlocksByRoot: async (_peer, roots) => - Array.from(roots) - .map((root) => blocksByRoot.get(toHexString(root))) - .filter(notNullish), - }; - let reportPeerResolveFn: (value: Parameters) => void; const reportPeerPromise = new Promise>((r) => (reportPeerResolveFn = r)); const network: Partial = { events: new NetworkEventBus(), getConnectedPeers: () => [peer], - reqResp: reqResp as IReqRespBeaconNode, + beaconBlocksMaybeBlobsByRoot: async (_peerId, roots) => + Array.from(roots) + .map((root) => blocksByRoot.get(toHexString(root))) + .filter(notNullish) + .map((block) => getBlockInput.preEIP4844(config, block)), + reportPeer: (peerId, action, actionName) => reportPeerResolveFn([peerId, action, actionName]), }; @@ -69,7 +68,7 @@ describe("sync / UnknownBlockSync", () => { const chain: Partial = { forkChoice: forkChoice as IForkChoice, - processBlock: async (block) => { + processBlock: async ({block}) => { if (!forkChoice.hasBlock(block.message.parentRoot)) throw Error("Unknown parent"); // Simluate adding the block to the forkchoice const blockRootHex = toHexString(ssz.phase0.BeaconBlock.hashTreeRoot(block.message)); @@ -78,7 +77,7 @@ describe("sync / UnknownBlockSync", () => { }; new UnknownBlockSync(config, network as INetwork, chain as IBeaconChain, logger, null); - network.events?.emit(NetworkEvent.unknownBlockParent, blockC, peerIdStr); + network.events?.emit(NetworkEvent.unknownBlockParent, getBlockInput.preEIP4844(config, blockC), peerIdStr); if (reportPeer) { const err = await reportPeerPromise;