Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2b61c86
feat: disable execution payload validation for beacon block on gloas
ahshum Oct 17, 2025
d49413a
chore: include gloas spec for beacon_block
ahshum Oct 17, 2025
411deae
feat: full block pool
ahshum Oct 17, 2025
48075c3
fix: case
ahshum Oct 17, 2025
46ad77e
feat: check block by root
ahshum Oct 17, 2025
8bdb4f8
feat: add validation for execution payload
ahshum Oct 17, 2025
1286ae7
feat: init full block pool
ahshum Oct 17, 2025
4c02aa0
feat: create execution payload gossip processor
ahshum Oct 17, 2025
4a81e7b
refactor: move toBlockId under gloas
ahshum Oct 17, 2025
6463ef4
test: add full block pool
ahshum Oct 17, 2025
f9295bd
fix: simplify checks
ahshum Oct 17, 2025
85cd2b6
chore: copyright year
ahshum Oct 20, 2025
dafc65a
fix: validation
ahshum Oct 20, 2025
16a1c56
test: update case name
ahshum Oct 20, 2025
a4e16b6
feat: log bid for fallback investigation
ahshum Oct 20, 2025
a2af12a
fix: specify beacon block type
ahshum Oct 20, 2025
d3f1aa2
chore: todo
ahshum Oct 20, 2025
8fce3e3
refactor: remove processing status
ahshum Oct 20, 2025
7950b9b
chore: free unused var
ahshum Oct 20, 2025
9abc8db
chore: import ordering
ahshum Oct 20, 2025
8b78858
Merge branch 'unstable' into sam/g
ahshum Oct 20, 2025
2f4d295
Merge branch 'unstable' into sam/g
ahshum Oct 20, 2025
4922fb1
fix: beacon time from cfg
ahshum Oct 20, 2025
59fab8f
refactor: check seen block from existing data
ahshum Oct 20, 2025
832e990
Merge branch 'unstable' into sam/g
ahshum Oct 21, 2025
7f13385
fix: beacon time from time params
ahshum Oct 21, 2025
b96fda8
fix: time params
ahshum Oct 21, 2025
b0fa3a5
Merge branch 'unstable' into sam/g
ahshum Oct 22, 2025
3dbd874
fix: time params
ahshum Oct 22, 2025
07bf838
fix: use table
ahshum Oct 22, 2025
a40ff27
refactor: remove unused function
ahshum Oct 22, 2025
1c440d7
refactor: rename processor
ahshum Oct 22, 2025
c0da29d
refactor: remove unused block execution enabled impl
ahshum Oct 22, 2025
b6058b6
revert: gloas beacon_block validation
ahshum Oct 22, 2025
cf8fb6e
revert: undo full block pool
ahshum Oct 22, 2025
5d5b8c5
refactor: execution payload gossip validation
ahshum Oct 22, 2025
df1600e
revert: extract changes
ahshum Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,13 @@ AllTests-mainnet
+ load phase0 state OK
+ should raise on unknown data OK
```
## Full block pool
```diff
+ Add envelope OK
+ Block has been seen OK
+ Envelope progress OK
+ Envelope status OK
```
## Gas limit management [Beacon Node] [Preset: mainnet]
```diff
+ Configuring the gas limit [Beacon Node] [Preset: mainnet] OK
Expand Down
201 changes: 201 additions & 0 deletions beacon_chain/consensus_object_pools/full_block_pool.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# beacon_chain
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import
stew/shims/hashes,
./[block_pools_types, block_quarantine],
../spec/[digest, forks]

type
ExecPayloadUniqKey = object
root: Eth2Digest
builderIdx: uint64

ExecPayloadEnvelopeStatus {.pure.} = enum
None = 0,
Valid

ExecPayloadEnvelopeProgress {.pure.} = enum
None = 0,
Processed

ExecPayloadEnvelopeDetail = object
envelope: SignedExecutionPayloadEnvelope
status: ExecPayloadEnvelopeStatus
progress: ExecPayloadEnvelopeProgress

BeaconBlockDetail = object
executionEnabled: bool

FullBlockPool* = object
## An experimental pool for keeping track of execution payload envelope and
## beacon block for constructing a full beacon block.

blocks: Table[Eth2Digest, BeaconBlockDetail]
## Blocks that received from the network.
envelopes: OrderedTable[ExecPayloadUniqKey, ExecPayloadEnvelopeDetail]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this an OrderedTable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be used for pruning old data. Or should I use Table for now and modify it when implementing date pruning?

## Execution payload envelopes that received from the network.
# TODO: persistent storage for valid envelope for the retention period

func hash*(x: ExecPayloadUniqKey): Hash =
hashAllFields(x)

func toExecPayloadUniqKey(
envelope: SignedExecutionPayloadEnvelope): ExecPayloadUniqKey =
ExecPayloadUniqKey(
root: envelope.message.beacon_block_root,
builderIdx: envelope.message.builder_index)

func toExecPayloadUniqKey(
blck: gloas.SignedBeaconBlock): ExecPayloadUniqKey =
template bid: untyped = blck.message.body.signed_execution_payload_bid
ExecPayloadUniqKey(
root: blck.root,
builderIdx: bid.message.builder_index)

func init*(
T: type FullBlockPool):
FullBlockPool =
debugGloasComment("")
T()

func pruneData*(pool: var FullBlockPool) =
debugGloasComment("")

func addEnvelope*(
pool: var FullBlockPool,
envelope: SignedExecutionPayloadEnvelope) =
pool.envelopes[envelope.toExecPayloadUniqKey()] =
ExecPayloadEnvelopeDetail(envelope: envelope)

func addBlock*(
pool: var FullBlockPool,
blck: ForkySignedBeaconBlock) =
pool.blocks[blck.root] = BeaconBlockDetail()

func checkEnvelopeStatus(
pool: FullBlockPool,
key: ExecPayloadUniqKey,
status: ExecPayloadEnvelopeStatus): bool =
try:
pool.envelopes[key].status == status
except KeyError:
false

func checkEnvelopeProgress(
pool: FullBlockPool,
key: ExecPayloadUniqKey,
progress: ExecPayloadEnvelopeProgress): bool =
try:
pool.envelopes[key].progress == progress
except KeyError:
false

func isEnvelopeSeen*(
pool: FullBlockPool,
envelope: SignedExecutionPayloadEnvelope): bool =
envelope.toExecPayloadUniqKey() in pool.envelopes

func isEnvelopeValid*(
pool: FullBlockPool,
envelope: SignedExecutionPayloadEnvelope): bool =
pool.checkEnvelopeStatus(envelope.toExecPayloadUniqKey(),
ExecPayloadEnvelopeStatus.Valid)

func isEnvelopeProcessed*(
pool: FullBlockPool,
envelope: SignedExecutionPayloadEnvelope): bool =
pool.checkEnvelopeProgress(envelope.toExecPayloadUniqKey(),
ExecPayloadEnvelopeProgress.Processed)

func isBlockExecutionEnabled*(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere, or would it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as execution payload is being removed from block, it would be used in the beacon_block to determine the route of validation.

pool: FullBlockPool,
blck: ForkySignedBeaconBlock): bool =
try:
pool.blocks[blck.root].executionEnabled
except KeyError:
false

func isBlockSeen*(
pool: FullBlockPool,
blockRoot: Eth2Digest): bool =
blockRoot in pool.blocks

func isBlockSeen*(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this overload used anywhere except the tests, or would it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It can be removed.

pool: FullBlockPool,
blck: ForkySignedBeaconBlock): bool =
pool.isBlockSeen(blck.root)

func getEnvelope(
pool: FullBlockPool,
key: ExecPayloadUniqKey):
Opt[SignedExecutionPayloadEnvelope] =
try:
Opt.some(pool.envelopes[key].envelope)
except KeyError:
Opt.none(SignedExecutionPayloadEnvelope)

func getEnvelope*(
pool: FullBlockPool,
envelope: SignedExecutionPayloadEnvelope):
Opt[SignedExecutionPayloadEnvelope] =
pool.getEnvelope(envelope.toExecPayloadUniqKey())

func getEnvelope*(
pool: FullBlockPool,
blck: ForkySignedBeaconBlock):
Opt[SignedExecutionPayloadEnvelope] =
when type(blck).kind >= Gloas:
pool.getEnvelope(blck.toExecPayloadUniqKey())
else:
Opt.none(SignedExecutionPayloadEnvelope)

func setEnvelopeStatus(
pool: var FullBlockPool,
key: ExecPayloadUniqKey,
status: ExecPayloadEnvelopeStatus) =
if key notin pool.envelopes:
return
try:
pool.envelopes[key].status = status
except KeyError:
return

func setEnvelopeProgress(
pool: var FullBlockPool,
key: ExecPayloadUniqKey,
progress: ExecPayloadEnvelopeProgress) =
if key notin pool.envelopes:
return
try:
pool.envelopes[key].progress = progress
except KeyError:
return

func markEnvelopeValid*(
pool: var FullBlockPool,
envelope: SignedExecutionPayloadEnvelope) =
pool.setEnvelopeStatus(envelope.toExecPayloadUniqKey(),
ExecPayloadEnvelopeStatus.Valid)

func markEnvelopeProcessed*(
pool: var FullBlockPool,
envelope: SignedExecutionPayloadEnvelope) =
pool.setEnvelopeProgress(envelope.toExecPayloadUniqKey(),
ExecPayloadEnvelopeProgress.Processed)

func markBlockExecutionEnabled*(
pool: var FullBlockPool,
blck: ForkySignedBeaconBlock) =
if blck.root notin pool.blocks:
return
try:
pool.blocks[blck.root].executionEnabled = true
except KeyError:
return
97 changes: 96 additions & 1 deletion beacon_chain/gossip_processing/eth2_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import
../spec/[helpers, forks],
../consensus_object_pools/[
blob_quarantine, block_clearance, block_quarantine, blockchain_dag,
attestation_pool, light_client_pool,
attestation_pool, full_block_pool, light_client_pool,
sync_committee_msg_pool, validator_change_pool],
../validators/validator_pool,
../beacon_clock,
Expand Down Expand Up @@ -131,6 +131,7 @@ type
validatorPool*: ref ValidatorPool
syncCommitteeMsgPool: ref SyncCommitteeMsgPool
lightClientPool: ref LightClientPool
fullBlockPool: ref FullBlockPool

doppelgangerDetection*: DoppelgangerProtection

Expand Down Expand Up @@ -178,6 +179,7 @@ proc new*(T: type Eth2Processor,
validatorPool: ref ValidatorPool,
syncCommitteeMsgPool: ref SyncCommitteeMsgPool,
lightClientPool: ref LightClientPool,
fullBlockPool: ref FullBlockPool,
quarantine: ref Quarantine,
blobQuarantine: ref BlobQuarantine,
dataColumnQuarantine: ref ColumnQuarantine,
Expand All @@ -197,6 +199,7 @@ proc new*(T: type Eth2Processor,
validatorPool: validatorPool,
syncCommitteeMsgPool: syncCommitteeMsgPool,
lightClientPool: lightClientPool,
fullBlockPool: fullBlockPool,
quarantine: quarantine,
blobQuarantine: blobQuarantine,
dataColumnQuarantine: dataColumnQuarantine,
Expand All @@ -215,6 +218,65 @@ proc new*(T: type Eth2Processor,
# any side effects until the message is fully validated, or invalid messages
# could be used to push out valid messages.

proc processExecutionPayloadGloas(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe processExecutionPayloadEnvelope instead of processExecutionPayloadGloas; it more usefully/accurately describes what it's doing. There will be other forks after Gloas, for which this function will still apply.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup sure that works for me. I was thinking it would be a temporary function for processing the execution payload from either beacon_block or execution_payload.

self: var Eth2Processor,
signedBlock: gloas.SignedBeaconBlock,
signedEnvelope: SignedExecutionPayloadEnvelope) =
## Process execution payload when both the block and envelope are found.

logScope:
blockRoot = shortLog(signedBlock.root)
builderIdx = signedEnvelope.message.builder_index

# only process once
if self.fullBlockPool[].isEnvelopeProcessed(signedEnvelope):
return

trace "Execution payload processing"
debugGloasComment("")

# process complete
debug "Execution payload processed"
self.fullBlockPool[].markEnvelopeProcessed(signedEnvelope)
self.fullBlockPool[].markBlockExecutionEnabled(signedBlock)

proc processExecutionPayloadGloas(
self: var Eth2Processor,
signedBlock: gloas.SignedBeaconBlock) =
## Received a valid block and checking if the envelope arrives

# check if the envelope exists
let signedEnvelope = self.fullBlockPool[].getEnvelope(signedBlock).valueOr:
return

# validate the envelope again as it wasn't validated without the block
if not self.fullBlockPool[].isEnvelopeValid(signedEnvelope):
self.dag.validateExecutionPayload(self.fullBlockPool, signedEnvelope).isOkOr:
return
self.fullBlockPool[].markEnvelopeValid(signedEnvelope)

# process
self.processExecutionPayloadGloas(signedBlock, signedEnvelope)

proc processExecutionPayloadGloas(
self: var Eth2Processor,
signedEnvelope: SignedExecutionPayloadEnvelope) =
## Received a valid envelope and the block should be in the chain

# find the block from the chain
let signedBlock =
block:
let forkedBlock = self.dag.getForkedBlock(signedEnvelope.toBlockId()).valueOr:
return
withBlck(forkedBlock):
when consensusFork >= ConsensusFork.Gloas:
forkyBlck.asSigned()
else:
return

# process
self.processExecutionPayloadGloas(signedBlock, signedEnvelope)

proc processSignedBeaconBlock*(
self: var Eth2Processor, src: MsgSource,
signedBlock: ForkySignedBeaconBlock,
Expand Down Expand Up @@ -276,6 +338,9 @@ proc processSignedBeaconBlock*(
else:
{.error: "Unknown fork " & $consensusFork.}

when type(signedBlock).kind >= ConsensusFork.Gloas:
self.processExecutionPayloadGloas(signedBlock)

let validationDur = nanoseconds((self.getCurrentBeaconTime() - wallTime).nanoseconds)
self.blockProcessor.enqueueBlock(
src, signedBlock, sidecarsOpt, maybeFinalized, validationDur
Expand All @@ -287,6 +352,36 @@ proc processSignedBeaconBlock*(

ok()

proc processExecutionPayload*(
self: var Eth2Processor, src: MsgSource,
signedEnvelope: SignedExecutionPayloadEnvelope):
ValidationRes =
let
wallTime = self.getCurrentBeaconTime()
(_, wallSlot) = wallTime.toSlot()

logScope:
blockRoot = shortLog(signedEnvelope.message.beacon_block_root)
builderIdx = signedEnvelope.message.builder_index
signature = shortLog(signedEnvelope.signature)
wallSlot

let delay = wallTime - signedEnvelope.message.slot.start_beacon_time
debug "Execution payload received", delay

# always save the envelope in case the block arrives later
self.fullBlockPool[].addEnvelope(signedEnvelope)

self.dag.validateExecutionPayload(self.fullBlockPool, signedEnvelope).isOkOr:
return err(error)

trace "Execution payload validated"

self.fullBlockPool[].markEnvelopeValid(signedEnvelope)
self.processExecutionPayloadGloas(signedEnvelope)

ok()

proc processBlobSidecar*(
self: var Eth2Processor, src: MsgSource,
blobSidecar: deneb.BlobSidecar, subnet_id: BlobId): ValidationRes =
Expand Down
Loading
Loading