From 7df198ea691c701d98fe70dacf8265656616f8e1 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:59:08 +0100 Subject: [PATCH 1/9] feat: add validation for gloas --- .../gossip_processing/gossip_validation.nim | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index cebb3cbd71..d92dc0acf1 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -428,6 +428,40 @@ template validateBeaconBlockDeneb( blob_params.MAX_BLOBS_PER_BLOCK): return dag.checkedReject("validateBeaconBlockDeneb: too many blob commitments") +template validateBeaconBlockGloas( + _: ChainDAGRef, + _: + phase0.SignedBeaconBlock | altair.SignedBeaconBlock | + bellatrix.SignedBeaconBlock | capella.SignedBeaconBlock | + deneb.SignedBeaconBlock | electra.SignedBeaconBlock | + fulu.SignedBeaconBlock): untyped = + discard + +# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block +template validateBeaconBlockGloas( + dag: ChainDAGRef, + signed_beacon_block: gloas.SignedBeaconBlock): untyped = + template blck: untyped = signed_beacon_block.message + template bid: untyped = blck.body.signed_execution_payload_bid.message + + # If `execution_payload` verification of block's execution payload parent by + # an execution node **is complete** + debugGloasComment("update is_execution_block") + if signed_beacon_block.is_execution_block: + # [REJECT] The block's execution payload parent (defined by + # `bid.parent_block_hash`) passes all validation. + withState(dag.headState): + when consensusFork >= ConsensusFork.Gloas: + if bid.parent_block_hash != dag.headState.latest_block_hash: + return dag.checkedReject("validateBeaconBlockGloas: invalid execution payload parent") + + # [REJECT] The bid's parent (defined by `bid.parent_block_root`) equals the + # block's parent (defined by `block.parent_root`). + if bid.parent_block_root != blck.parent_root: + return dag.checkedReject("validateBeaconBlockGloas: parent block root mismatch") + + ok() + # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/deneb/p2p-interface.md#blob_sidecar_subnet_id proc validateBlobSidecar*( dag: ChainDAGRef, quarantine: ref Quarantine, @@ -937,6 +971,8 @@ proc validateBeaconBlock*( dag.validateBeaconBlockDeneb(signed_beacon_block, wallTime) + dag.validateBeaconBlockGloas(signed_beacon_block) + # [REJECT] The block is from a higher slot than its parent. if not (signed_beacon_block.message.slot > parent.bid.slot): return dag.checkedReject( From 249b99844f82d3cd71c9c37b87107f7a273ac662 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:59:53 +0100 Subject: [PATCH 2/9] fix: spec update and remove gloasComment --- beacon_chain/gossip_processing/gossip_validation.nim | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index d92dc0acf1..f40efdc726 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -340,8 +340,7 @@ func getMaxBlobsPerBlock(cfg: RuntimeConfig, slot: Slot): uint64 = else: cfg.MAX_BLOBS_PER_BLOCK -debugGloasComment "" -# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.0/specs/gloas/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockBellatrix( _: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | gloas.SignedBeaconBlock, _: BlockRef): untyped = @@ -401,8 +400,7 @@ template validateBeaconBlockBellatrix( # cannot occur here, because Nimbus's optimistic sync waits for either # `ACCEPTED` or `SYNCING` from the EL to get this far. -debugGloasComment "" -# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.0/specs/gloas/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockDeneb( _: ChainDAGRef, _: From 500375f5deb557db2eb35b6452c148b861240c63 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:04:53 +0100 Subject: [PATCH 3/9] fix: gloas block validation --- .../gossip_processing/gossip_validation.nim | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index f40efdc726..956b793ad9 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -432,20 +432,33 @@ template validateBeaconBlockGloas( phase0.SignedBeaconBlock | altair.SignedBeaconBlock | bellatrix.SignedBeaconBlock | capella.SignedBeaconBlock | deneb.SignedBeaconBlock | electra.SignedBeaconBlock | - fulu.SignedBeaconBlock): untyped = + fulu.SignedBeaconBlock, + _: BlockRef): untyped = discard # https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockGloas( dag: ChainDAGRef, - signed_beacon_block: gloas.SignedBeaconBlock): untyped = + signed_beacon_block: gloas.SignedBeaconBlock, + parent: BlockRef): untyped = template blck: untyped = signed_beacon_block.message template bid: untyped = blck.body.signed_execution_payload_bid.message # If `execution_payload` verification of block's execution payload parent by # an execution node **is complete** - debugGloasComment("update is_execution_block") - if signed_beacon_block.is_execution_block: + debugGloasComment("") + let isExecutionEnabled = + if signed_beacon_block.message.is_execution_block: + true + else: + # If we don't know whether the parent block had execution enabled, + # assume it didn't. This way, we don't reject here if the timestamp + # is invalid, and let state transition check the timestamp. + # This is an edge case, and may be hit in a pathological scenario with + # checkpoint sync, because the checkpoint block may be unavailable + # and it could already be the parent of the new block before backfill. + not dag.loadExecutionBlockHash(parent).get(ZERO_HASH).isZero + if isExecutionEnabled: # [REJECT] The block's execution payload parent (defined by # `bid.parent_block_hash`) passes all validation. withState(dag.headState): @@ -969,7 +982,7 @@ proc validateBeaconBlock*( dag.validateBeaconBlockDeneb(signed_beacon_block, wallTime) - dag.validateBeaconBlockGloas(signed_beacon_block) + dag.validateBeaconBlockGloas(signed_beacon_block, parent) # [REJECT] The block is from a higher slot than its parent. if not (signed_beacon_block.message.slot > parent.bid.slot): From c7306f046e47ef390c40982274e7ce7ed0f40bb2 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:15:47 +0100 Subject: [PATCH 4/9] fix: state field --- beacon_chain/gossip_processing/gossip_validation.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 956b793ad9..9e09cd2438 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -463,7 +463,7 @@ template validateBeaconBlockGloas( # `bid.parent_block_hash`) passes all validation. withState(dag.headState): when consensusFork >= ConsensusFork.Gloas: - if bid.parent_block_hash != dag.headState.latest_block_hash: + if bid.parent_block_hash != forkyState.latest_block_hash: return dag.checkedReject("validateBeaconBlockGloas: invalid execution payload parent") # [REJECT] The bid's parent (defined by `bid.parent_block_root`) equals the From 61a6170b5a8482b974bc23780689174ca405f9aa Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:39:41 +0100 Subject: [PATCH 5/9] fix: template var and return --- beacon_chain/gossip_processing/gossip_validation.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 9e09cd2438..e13a00af62 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -463,7 +463,7 @@ template validateBeaconBlockGloas( # `bid.parent_block_hash`) passes all validation. withState(dag.headState): when consensusFork >= ConsensusFork.Gloas: - if bid.parent_block_hash != forkyState.latest_block_hash: + if bid.parent_block_hash != forkyState.data.latest_block_hash: return dag.checkedReject("validateBeaconBlockGloas: invalid execution payload parent") # [REJECT] The bid's parent (defined by `bid.parent_block_root`) equals the @@ -471,8 +471,6 @@ template validateBeaconBlockGloas( if bid.parent_block_root != blck.parent_root: return dag.checkedReject("validateBeaconBlockGloas: parent block root mismatch") - ok() - # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/deneb/p2p-interface.md#blob_sidecar_subnet_id proc validateBlobSidecar*( dag: ChainDAGRef, quarantine: ref Quarantine, From 238990f2b60e94277c8b72df8f3bc2f9f18bcba5 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:59:56 +0000 Subject: [PATCH 6/9] feat: update helper for Gloas --- beacon_chain/consensus_object_pools/blockchain_dag.nim | 5 ++--- beacon_chain/spec/helpers.nim | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 791e217062..5c7fa1a95b 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -2283,9 +2283,8 @@ proc loadExecutionBlockHash*(dag: ChainDAGRef, bid: BlockId): Opt[Eth2Digest] = return Opt.none(Eth2Digest) withBlck(blockData): - debugGloasComment " " - when consensusFork == ConsensusFork.Gloas: - Opt.some ZERO_HASH + when consensusFork >= ConsensusFork.Gloas: + Opt.some forkyBlck.message.body.signed_execution_payload_bid.message.block_hash elif consensusFork >= ConsensusFork.Bellatrix: Opt.some forkyBlck.message.body.execution_payload.block_hash else: diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index aee9a93658..84f5f2e997 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -400,9 +400,9 @@ func is_merge_transition_complete*(state: gloas.BeaconState): bool = # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/sync/optimistic.md#helpers func is_execution_block*(body: SomeForkyBeaconBlockBody): bool = - when typeof(body).kind == ConsensusFork.Gloas: - debugGloasComment "" - false + when typeof(body).kind >= ConsensusFork.Gloas: + # Execution payload should always be enabled since Gloas. + true elif typeof(body).kind >= ConsensusFork.Bellatrix: const defaultExecutionPayload = default(typeof(body.execution_payload)) body.execution_payload != defaultExecutionPayload From 6d0ac38880b949d6f29b35f7d6edf5716cad12ab Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:00:40 +0000 Subject: [PATCH 7/9] fix: update validations --- .../gossip_processing/gossip_validation.nim | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index e13a00af62..e38b491aac 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -400,7 +400,7 @@ template validateBeaconBlockBellatrix( # cannot occur here, because Nimbus's optimistic sync waits for either # `ACCEPTED` or `SYNCING` from the EL to get this far. -# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockDeneb( _: ChainDAGRef, _: @@ -432,44 +432,43 @@ template validateBeaconBlockGloas( phase0.SignedBeaconBlock | altair.SignedBeaconBlock | bellatrix.SignedBeaconBlock | capella.SignedBeaconBlock | deneb.SignedBeaconBlock | electra.SignedBeaconBlock | - fulu.SignedBeaconBlock, - _: BlockRef): untyped = + fulu.SignedBeaconBlock): untyped = discard -# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockGloas( dag: ChainDAGRef, - signed_beacon_block: gloas.SignedBeaconBlock, - parent: BlockRef): untyped = + signed_beacon_block: gloas.SignedBeaconBlock): untyped = template blck: untyped = signed_beacon_block.message template bid: untyped = blck.body.signed_execution_payload_bid.message - # If `execution_payload` verification of block's execution payload parent by - # an execution node **is complete** - debugGloasComment("") - let isExecutionEnabled = - if signed_beacon_block.message.is_execution_block: - true - else: - # If we don't know whether the parent block had execution enabled, - # assume it didn't. This way, we don't reject here if the timestamp - # is invalid, and let state transition check the timestamp. - # This is an edge case, and may be hit in a pathological scenario with - # checkpoint sync, because the checkpoint block may be unavailable - # and it could already be the parent of the new block before backfill. - not dag.loadExecutionBlockHash(parent).get(ZERO_HASH).isZero - if isExecutionEnabled: - # [REJECT] The block's execution payload parent (defined by - # `bid.parent_block_hash`) passes all validation. - withState(dag.headState): - when consensusFork >= ConsensusFork.Gloas: - if bid.parent_block_hash != forkyState.data.latest_block_hash: - return dag.checkedReject("validateBeaconBlockGloas: invalid execution payload parent") + # If execution_payload verification of block's execution payload parent by an + # execution node is complete: + # + # - [REJECT] The block's execution payload parent (defined by + # bid.parent_block_hash) passes all validation. + let + parentRef = dag.getBlockRef(bid.parent_block_root) + parentBlock = + block: + if parentRef.isSome(): + dag.getForkedBlock(parentRef.get().bid) + else: + Opt.none(ForkedTrustedSignedBeaconBlock) + if parentBlock.isSome(): + withBlck(parentBlock.get()): + if forkyBlck.message.is_execution_block: + let parentHash = dag.loadExecutionBlockHash(parentRef.get()).valueOr: + return dag.checkedReject( + "validateBeaconBlockGloas: invalid execution parent") + if not (bid.parent_block_hash == parentHash): + return dag.checkedReject( + "validateBeaconBlockGloas: invalid execution parent") # [REJECT] The bid's parent (defined by `bid.parent_block_root`) equals the # block's parent (defined by `block.parent_root`). - if bid.parent_block_root != blck.parent_root: - return dag.checkedReject("validateBeaconBlockGloas: parent block root mismatch") + if not (bid.parent_block_root == blck.parent_root): + return dag.checkedReject("validateBeaconBlockGloas: parent block mismatch") # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/deneb/p2p-interface.md#blob_sidecar_subnet_id proc validateBlobSidecar*( @@ -851,6 +850,7 @@ proc validateDataColumnSidecar*( # https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/p2p-interface.md#beacon_block # https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/bellatrix/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/p2p-interface.md#beacon_block proc validateBeaconBlock*( dag: ChainDAGRef, quarantine: ref Quarantine, signed_beacon_block: ForkySignedBeaconBlock, @@ -931,9 +931,10 @@ proc validateBeaconBlock*( quarantine[].addOrphan(dag.finalizedHead.slot, signed_beacon_block).isOkOr: # Queueing failed because the parent was unviable - this means this block # is unviable as well, for the same reason - return - case error - of UnviableKind.Invalid: + case error + of UnviableKind.Invalid: + when typeof(signed_beacon_block).kind <= ConsensusFork.Fulu: + # These checks are removed in Gloas. if signed_beacon_block.message.is_execution_block: # https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/bellatrix/p2p-interface.md#beacon_block # @@ -960,11 +961,11 @@ proc validateBeaconBlock*( # whether it was marked unviable due to consensus (REJECT) or # execution (IGNORE) verification failure. We err on the IGNORE side. # TODO track this as a separate UnviableKind - errIgnore("BeaconBlock: parent invalid") + return errIgnore("BeaconBlock: parent invalid") else: - errReject("BeaconBlock: parent invalid") - of UnviableKind.UnviableFork: - errIgnore("BeaconBlock: parent from unviable fork") + return errReject("BeaconBlock: parent invalid") + of UnviableKind.UnviableFork: + return errIgnore("BeaconBlock: parent from unviable fork") debug "Block quarantined", blockRoot = shortLog(signed_beacon_block.root), @@ -980,7 +981,7 @@ proc validateBeaconBlock*( dag.validateBeaconBlockDeneb(signed_beacon_block, wallTime) - dag.validateBeaconBlockGloas(signed_beacon_block, parent) + dag.validateBeaconBlockGloas(signed_beacon_block) # [REJECT] The block is from a higher slot than its parent. if not (signed_beacon_block.message.slot > parent.bid.slot): From d2153c450385f1f79d4d09472bf858d2e5d979e6 Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:10:18 +0000 Subject: [PATCH 8/9] chore: cleanup --- beacon_chain/gossip_processing/gossip_validation.nim | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index e38b491aac..24ccbdc47c 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -450,11 +450,10 @@ template validateBeaconBlockGloas( let parentRef = dag.getBlockRef(bid.parent_block_root) parentBlock = - block: - if parentRef.isSome(): - dag.getForkedBlock(parentRef.get().bid) - else: - Opt.none(ForkedTrustedSignedBeaconBlock) + if parentRef.isSome(): + dag.getForkedBlock(parentRef.get().bid) + else: + Opt.none(ForkedTrustedSignedBeaconBlock) if parentBlock.isSome(): withBlck(parentBlock.get()): if forkyBlck.message.is_execution_block: From e244313ff04aa54dbf7345e6874d078187f8146c Mon Sep 17 00:00:00 2001 From: Sam Shum <4080806+ahshum@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:45:50 +0000 Subject: [PATCH 9/9] chore: update spec url --- beacon_chain/gossip_processing/gossip_validation.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 24ccbdc47c..b6450201c4 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -340,7 +340,7 @@ func getMaxBlobsPerBlock(cfg: RuntimeConfig, slot: Slot): uint64 = else: cfg.MAX_BLOBS_PER_BLOCK -# https://github.com/ethereum/consensus-specs/blob/v1.6.0-beta.1/specs/gloas/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/p2p-interface.md#beacon_block template validateBeaconBlockBellatrix( _: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | gloas.SignedBeaconBlock, _: BlockRef): untyped = @@ -847,8 +847,8 @@ proc validateDataColumnSidecar*( ok() -# https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/p2p-interface.md#beacon_block -# https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/bellatrix/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/p2p-interface.md#beacon_block +# https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/bellatrix/p2p-interface.md#beacon_block # https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/p2p-interface.md#beacon_block proc validateBeaconBlock*( dag: ChainDAGRef, quarantine: ref Quarantine,