diff --git a/packages/beacon-node/src/chain/blocks/index.ts b/packages/beacon-node/src/chain/blocks/index.ts index f3dcbda3bb6d..374683a5471e 100644 --- a/packages/beacon-node/src/chain/blocks/index.ts +++ b/packages/beacon-node/src/chain/blocks/index.ts @@ -108,11 +108,9 @@ export async function processBlocks( throw segmentExecStatus.execAborted.execError; } - if (opts.skipVerifyBlockSignatures !== true) { - for (const blockInput of relevantBlocks) { - const block = blockInput.getBlock().message; - this.seenBlockProposers.add(block.slot, block.proposerIndex); - } + for (const blockInput of relevantBlocks) { + const block = blockInput.getBlock().message; + this.seenBlockProposers.add(block.slot, block.proposerIndex); } const {executionStatuses} = segmentExecStatus; diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 71edca41c89e..97224f9d92ed 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -6,9 +6,9 @@ import { computeEpochAtSlot, signedBlockToSignedHeader, } from "@lodestar/state-transition"; -import {IndexedAttestation, Slot, deneb, ssz} from "@lodestar/types"; -import {toRootHex} from "@lodestar/utils"; +import {IndexedAttestation, Slot, deneb} from "@lodestar/types"; import {getBlobKzgCommitments} from "../../util/dataColumns.js"; +import {callInNextEventLoop} from "../../util/eventLoop.js"; import type {BeaconChain} from "../chain.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockProcessOpts} from "../options.js"; @@ -193,34 +193,30 @@ export async function verifyBlocksInEpoch( ), // All signatures at once - opts.skipVerifyBlockSignatures !== true - ? verifyBlocksSignatures( - this.config, - this.bls, - this.logger, - this.metrics, - preState0, - blocks, - indexedAttestationsByBlock, - opts - ) - : Promise.resolve({verifySignaturesTime: Date.now()}), + verifyBlocksSignatures( + this.config, + this.bls, + this.logger, + this.metrics, + preState0, + blocks, + indexedAttestationsByBlock, + opts + ), // TODO GLOAS: can verify payload signatures in batch too // maybe chain with the above verifyBlocksSignatures() ]); - if (opts.skipVerifyBlockSignatures !== true) { - for (const block of blocks) { - const {slot, proposerIndex} = block.message; - const signedBlockHeader = signedBlockToSignedHeader(this.config, block); - const blockRoot = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(signedBlockHeader.message)); - this.seenBlockProposers.observeBlockRoot(slot, proposerIndex, blockRoot, signedBlockHeader); - // Only produce a slashing while importing the block. A block that is verified before it is published - // must not be treated as equivocation evidence since it may never be seen by the network - if (opts.verifyOnly !== true && this.seenBlockProposers.isEquivocating(slot, proposerIndex)) { - this.processProposerEquivocation(slot, proposerIndex); - } + for (const blockInput of blockInputs) { + const block = blockInput.getBlock(); + const {slot, proposerIndex} = block.message; + const signedBlockHeader = signedBlockToSignedHeader(this.config, block); + this.seenBlockProposers.observeBlockRoot(slot, proposerIndex, blockInput.blockRootHex, signedBlockHeader); + // Only produce a slashing while importing the block. A block that is verified before it is published + // must not be treated as equivocation evidence since it may never be seen by the network + if (opts.verifyOnly !== true && this.seenBlockProposers.isEquivocating(slot, proposerIndex)) { + callInNextEventLoop(() => this.processProposerEquivocation(slot, proposerIndex)); } } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 56609b81a0c7..73424830e98e 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1217,7 +1217,12 @@ export class BeaconChain implements IBeaconChain { this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing); this.emitter.emit(ChainEvent.publishProposerSlashing, proposerSlashing); this.metrics?.opPool.proposerSlashingsProduced.inc(); - this.logger.info("Produced proposer slashing from observed equivocation", {slot: blockSlot, proposerIndex}); + this.logger.info("Produced proposer slashing from observed equivocation", { + slot: blockSlot, + proposerIndex, + header1Root: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(header1.message)), + header2Root: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(header2.message)), + }); } finally { this.producingProposerSlashing.delete(proposerIndex); } diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index 1400c63be4cf..265d3f5ddd02 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -86,8 +86,6 @@ export type BlockProcessOpts = { verifyOnly?: boolean; /** Used to specify to skip execution payload validation */ skipVerifyExecutionPayload?: boolean; - /** Used to specify to skip block signatures validation */ - skipVerifyBlockSignatures?: boolean; }; export type PoolOpts = { diff --git a/packages/beacon-node/src/chain/seenCache/seenBlockProposers.ts b/packages/beacon-node/src/chain/seenCache/seenBlockProposers.ts index 309497cf2a92..d02454a8b8ef 100644 --- a/packages/beacon-node/src/chain/seenCache/seenBlockProposers.ts +++ b/packages/beacon-node/src/chain/seenCache/seenBlockProposers.ts @@ -3,7 +3,7 @@ import {Epoch, RootHex, Slot, ValidatorIndex, phase0} from "@lodestar/types"; import {MapDef} from "@lodestar/utils"; /** Two distinct block roots signed by the same proposer for the same slot are sufficient to establish an equivocation */ -const MAX_BLOCK_ROOTS_PER_PROPOSAL = 2; +const MIN_EQUIVOCATION_BLOCK_ROOTS_PER_PROPOSAL = 2; /** * Keeps a cache to filter block proposals from the same validator in the same slot. @@ -36,7 +36,8 @@ export class SeenBlockProposers { isEquivocating(blockSlot: Slot, proposerIndex: ValidatorIndex): boolean { return ( - (this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex)?.size ?? 0) >= MAX_BLOCK_ROOTS_PER_PROPOSAL + (this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex)?.size ?? 0) >= + MIN_EQUIVOCATION_BLOCK_ROOTS_PER_PROPOSAL ); } @@ -53,7 +54,10 @@ export class SeenBlockProposers { proposerIndex: ValidatorIndex ): [phase0.SignedBeaconBlockHeader, phase0.SignedBeaconBlockHeader] | null { const signedBlockHeaderByRoot = this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex); - if (signedBlockHeaderByRoot === undefined || signedBlockHeaderByRoot.size < MAX_BLOCK_ROOTS_PER_PROPOSAL) { + if ( + signedBlockHeaderByRoot === undefined || + signedBlockHeaderByRoot.size < MIN_EQUIVOCATION_BLOCK_ROOTS_PER_PROPOSAL + ) { return null; } const [signedHeader1, signedHeader2] = signedBlockHeaderByRoot.values(); @@ -72,7 +76,10 @@ export class SeenBlockProposers { } const signedBlockHeaderByRoot = this.signedBlockHeadersBySlot.getOrDefault(blockSlot).getOrDefault(proposerIndex); - if (signedBlockHeaderByRoot.size < MAX_BLOCK_ROOTS_PER_PROPOSAL && !signedBlockHeaderByRoot.has(blockRoot)) { + if ( + signedBlockHeaderByRoot.size < MIN_EQUIVOCATION_BLOCK_ROOTS_PER_PROPOSAL && + !signedBlockHeaderByRoot.has(blockRoot) + ) { signedBlockHeaderByRoot.set(blockRoot, signedBlockHeader); } } 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 index e846e50510e5..7a4ddbdd312f 100644 --- 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 @@ -129,8 +129,6 @@ describe("api - beacon - publishBlockV2", () => { await api.publishBlockV2({signedBlockContents: {signedBlock}, broadcastValidation}); expect(verifyBlocksInEpoch).toHaveBeenCalledOnce(); - const verifyOpts = vi.mocked(verifyBlocksInEpoch).mock.calls[0][3]; - expect(verifyOpts.skipVerifyBlockSignatures).not.toBe(true); expect(modules.network.publishBeaconBlock).toHaveBeenCalledWith(signedBlock); } );