Skip to content
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
52 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
9622532
feat: add envelope quarantine
ahshum Nov 3, 2025
44b938a
revert: envelope block id
ahshum Nov 4, 2025
b45bdd0
feat: add envelope table
ahshum Nov 4, 2025
b3003a9
feat: update gossip handler
ahshum Nov 4, 2025
d2a3713
Merge branch 'unstable'
ahshum Nov 4, 2025
168bd04
test: envelope quarantine
ahshum Nov 5, 2025
6383065
fix: func to use
ahshum Nov 5, 2025
21b5697
merge unstable
ahshum Nov 5, 2025
53413a7
fix: verify sig
ahshum Nov 5, 2025
03cd38f
fix: add missing by root
ahshum Nov 6, 2025
f9e4a6b
merge unstable
ahshum Nov 6, 2025
f60dfa3
chore: cleanup
ahshum Nov 6, 2025
a86e3c6
fix: style and comment
ahshum Nov 10, 2025
58b37fd
merge unstable
ahshum Nov 10, 2025
0c2ee81
fix: func call
ahshum Nov 10, 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 @@ -589,6 +589,13 @@ AllTests-mainnet
+ Roundtrip engine RPC V2 and capella ExecutionPayload representations OK
+ Roundtrip engine RPC V3 and deneb ExecutionPayload representations OK
```
## Envelope Quarantine
```diff
+ Add missing OK
+ Add orphan OK
+ Clean up orphans OK
+ Pop orphan OK
```
## Eth1 monitor
```diff
+ Rewrite URLs OK
Expand Down
22 changes: 22 additions & 0 deletions beacon_chain/beacon_chain_db.nim
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type

stateRoots: KvStoreRef # (Slot, BlockRoot) -> StateRoot

envelopes: KvStoreRef # (BlockRoot -> SignedExecutionPayloadEnvelope)

statesNoVal: array[ConsensusFork, KvStoreRef] # StateRoot -> ForkBeaconStateNoImmutableValidators

stateDiffs: KvStoreRef ##\
Expand Down Expand Up @@ -604,6 +606,10 @@ proc new*(T: type BeaconChainDB,
if cfg.FULU_FORK_EPOCH != FAR_FUTURE_EPOCH:
columns = kvStore db.openKvStore("fulu_columns").expectDb()

var envelopes: KvStoreRef
if cfg.GLOAS_FORK_EPOCH != FAR_FUTURE_EPOCH:
envelopes = kvStore db.openKvStore("gloas_envelopes").expectDb()

let quarantine = db.initQuarantineDB().expectDb()

# Versions prior to 1.4.0 (altair) stored validators in `immutable_validators`
Expand Down Expand Up @@ -642,6 +648,7 @@ proc new*(T: type BeaconChainDB,
blocks: blocks,
blobs: blobs,
columns: columns,
envelopes: envelopes,
stateRoots: stateRoots,
statesNoVal: statesNoVal,
stateDiffs: stateDiffs,
Expand Down Expand Up @@ -866,6 +873,14 @@ proc delDataColumnSidecar*(
root: Eth2Digest, index: ColumnIndex): bool =
db.columns.del(columnkey(root, index)).expectDb()

proc putExecutionPayloadEnvelope*(
db: BeaconChainDB, value: SignedExecutionPayloadEnvelope) =
template key: untyped = value.message.beacon_block_root
db.envelopes.putSZSSZ(key.data, value)

proc delExecutionPayloadEnvelope*(db: BeaconChainDB, root: Eth2Digest): bool =
db.envelopes.del(root.data).expectDb()

proc updateImmutableValidators*(
db: BeaconChainDB, validators: openArray[Validator]) =
# Must be called before storing a state that references the new validators
Expand Down Expand Up @@ -1078,6 +1093,13 @@ proc getDataColumnSidecar*(db: BeaconChainDB, root: Eth2Digest, index: ColumnInd
return false
db.columns.getSZSSZ(columnkey(root, index), value) == GetResult.found

proc getExecutionPayloadEnvelope*(
db: BeaconChainDB, root: Eth2Digest,
value: var TrustedSignedExecutionPayloadEnvelope): bool =
if db.envelopes == nil:
return false
db.envelopes.getSZSSZ(root.data, value) == GetResult.found

proc getBlockSZ*[X: ForkyTrustedSignedBeaconBlock](
db: BeaconChainDB, key: Eth2Digest,
data: var seq[byte], T: typedesc[X]): bool =
Expand Down
76 changes: 76 additions & 0 deletions beacon_chain/consensus_object_pools/envelope_quarantine.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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: [], gcsafe.}

import std/tables
import ../spec/[digest, forks]

type
EnvelopeQuarantine* = object
orphans*: Table[Eth2Digest, Table[uint64, SignedExecutionPayloadEnvelope]]
## Envelopes that we have received but did not have a block yet. In the
## ideal scenario, block should arrive before envelope but that is not
## guaranteed.

missing*: HashSet[Eth2Digest]
## List of block roots that we would like to have the envelopes but we
## have not got yet. Missing envelopes should usually be found when we
## received a block, blob or data column.

func init*(T: typedesc[EnvelopeQuarantine]): T =
T()

template root(v: SignedExecutionPayloadEnvelope): Eth2Digest =
v.message.beacon_block_root

func addMissing*(
self: var EnvelopeQuarantine,
root: Eth2Digest) =
self.missing.incl(root)

func addOrphan*(
self: var EnvelopeQuarantine,
envelope: SignedExecutionPayloadEnvelope) =
discard self.orphans
.mgetOrPut(envelope.root)
.hasKeyOrPut(envelope.message.builder_index, envelope)

func popOrphan*(
self: var EnvelopeQuarantine,
blck: gloas.SignedBeaconBlock,
): Opt[SignedExecutionPayloadEnvelope] =
if blck.root notin self.orphans:
return Opt.none(SignedExecutionPayloadEnvelope)

template builderIdx: untyped =
blck.message.body.signed_execution_payload_bid.message.builder_index
try:
var envelope: SignedExecutionPayloadEnvelope
if self.orphans[blck.root].pop(builderIdx, envelope):
Opt.some(envelope)
else:
Opt.none(SignedExecutionPayloadEnvelope)
except KeyError:
Opt.none(SignedExecutionPayloadEnvelope)
finally:
# After poping an envelope by block, the rest will no longer be valid due to
# the mismatch builder index.
self.orphans.del(blck.root)

func cleanupOrphans*(self: var EnvelopeQuarantine, finalizedSlot: Slot) =
var toDel: seq[Eth2Digest]

for k, v in self.orphans:
for _, e in v:
if finalizedSlot >= e.message.slot:
toDel.add(k)
# check only the first envelope as slot should be the same by block root.
break

for k in toDel:
self.orphans.del(k)
48 changes: 44 additions & 4 deletions beacon_chain/gossip_processing/eth2_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import
../el/el_manager,
../spec/[helpers, forks],
../consensus_object_pools/[
blob_quarantine, block_clearance, block_quarantine, blockchain_dag,
attestation_pool, execution_payload_pool, light_client_pool,
sync_committee_msg_pool, validator_change_pool],
attestation_pool, blob_quarantine, block_clearance, block_quarantine,
blockchain_dag, envelope_quarantine, execution_payload_pool,
light_client_pool, sync_committee_msg_pool, validator_change_pool],
../validators/validator_pool,
../beacon_clock,
"."/[gossip_validation, block_processor, batch_validation],
Expand All @@ -45,6 +45,10 @@ declareCounter beacon_blocks_received,
"Number of valid blocks processed by this node"
declareCounter beacon_blocks_dropped,
"Number of invalid blocks dropped by this node", labels = ["reason"]
declareCounter execution_payload_envelopes_received,
"Number of valid execution payload envelope processed by this node"
declareCounter execution_payload_envelopes_dropped,
"Number of invalid execution payload envelope dropped by this node", labels = ["reason"]
declareCounter blob_sidecars_received,
"Number of valid blobs processed by this node"
declareCounter blob_sidecars_dropped,
Expand Down Expand Up @@ -86,7 +90,7 @@ declareCounter beacon_execution_payload_bids_received,
"Number of valid execution payload bids processed by this node"

declareCounter beacon_execution_payload_bids_dropped,
"Number of invalid execution payload bids dropped by this node",
"Number of invalid execution payload bids dropped by this node",
labels = ["reason"]

const delayBuckets = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, Inf]
Expand All @@ -100,6 +104,9 @@ declareHistogram beacon_aggregate_delay,
declareHistogram beacon_block_delay,
"Time(s) between slot start and beacon block reception", buckets = delayBuckets

declareHistogram execution_payload_envelope_delay,
"Time(s) between slot start and execution payload envelope reception", buckets = delayBuckets

declareHistogram blob_sidecar_delay,
"Time(s) between slot start and blob sidecar reception", buckets = delayBuckets

Expand Down Expand Up @@ -165,6 +172,8 @@ type

dataColumnQuarantine*: ref ColumnQuarantine

envelopeQuarantine*: ref EnvelopeQuarantine

# Application-provided current time provider (to facilitate testing)
getCurrentBeaconTime*: GetBeaconTimeFn

Expand Down Expand Up @@ -305,6 +314,37 @@ proc processSignedBeaconBlock*(

ok()

proc processExecutionPayloadEnvelope*(
self: var Eth2Processor, src: MsgSource,
signedEnvelope: SignedExecutionPayloadEnvelope): ValidationRes =
let
wallTime = self.getCurrentBeaconTime()
(afterGenesis, wallSlot) = wallTime.toSlot(self.dag.timeParams)

logScope:
blockRoot = shortLog(signedEnvelope.message.beacon_block_root)
envelope = shortLog(signedEnvelope.message)
wallSlot

if not afterGenesis:
notice "Execution payload envelope before genesis"
return errIgnore("Execution payload envelope before genesis")

let delay = wallTime -
signedEnvelope.message.slot.start_beacon_time(self.dag.timeParams)

self.dag.validateExecutionPayload(
self.quarantine, self.envelopeQuarantine, signedEnvelope).isOkOr:
execution_payload_envelopes_dropped.inc(1, [$error[0]])
return err(error)

debugGloasComment("process execution payload")

execution_payload_envelopes_received.inc()
execution_payload_envelope_delay.observe(delay.toFloatSeconds())

ok()

proc processBlobSidecar*(
self: var Eth2Processor, src: MsgSource,
blobSidecar: deneb.BlobSidecar, subnet_id: BlobId): ValidationRes =
Expand Down
94 changes: 92 additions & 2 deletions beacon_chain/gossip_processing/gossip_validation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import
helpers, network, signatures, peerdas_helpers],
../consensus_object_pools/[
attestation_pool, blockchain_dag, blob_quarantine, block_clearance,
block_quarantine, execution_payload_pool, spec_cache, light_client_pool,
sync_committee_msg_pool, validator_change_pool],
block_quarantine, envelope_quarantine, execution_payload_pool,
light_client_pool, spec_cache, sync_committee_msg_pool,
validator_change_pool],
".."/[beacon_clock],
./batch_validation

Expand Down Expand Up @@ -952,6 +953,95 @@ proc validateBeaconBlock*(

ok()

# https://github.com/ethereum/consensus-specs/blob/v1.6.0/specs/gloas/p2p-interface.md#execution_payload
proc validateExecutionPayload*(
dag: ChainDAGRef, quarantine: ref Quarantine,
envelopeQuarantine: ref EnvelopeQuarantine,
signed_execution_payload_envelope: SignedExecutionPayloadEnvelope):
Result[void, ValidationError] =
template envelope: untyped = signed_execution_payload_envelope.message

# [IGNORE] The envelope's block root envelope.block_root has been seen (via
# gossip or non-gossip sources) (a client MAY queue payload for processing
# once the block is retrieved).
let blockSeen =
block:
var seen =
envelope.beacon_block_root in quarantine.unviable or
envelope.beacon_block_root in quarantine.missing or
dag.getBlockRef(envelope.beacon_block_root).isSome()
if not seen:
for k, _ in quarantine.orphans:
if k[0] == envelope.beacon_block_root:
seen = true
break
seen
if not blockSeen:
quarantine[].addMissing(envelope.beacon_block_root)
envelopeQuarantine[].addOrphan(signed_execution_payload_envelope)
return errIgnore("ExecutionPayload: block not found")

# [IGNORE] The node has not seen another valid SignedExecutionPayloadEnvelope
# for this block root from this builder.
#
# Validation of an envelope requires a valid block. There is a check to ensure
# that the builder index are the same from the envelope and the bid from the
# block. Meaning that checking builder index here would not be helpful due to
# the check later.
var validEnvelope: TrustedSignedExecutionPayloadEnvelope
if dag.db.getExecutionPayloadEnvelope(
envelope.beacon_block_root, validEnvelope):
return errIgnore("ExecutionPayload: already seen")

# [IGNORE] The envelope is from a slot greater than or equal to the latest
# finalized slot -- i.e. validate that `envelope.slot >=
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`
if envelope.slot <= dag.finalizedHead.slot:
return errIgnore("ExecutionPayload: slot already finalized")

# [REJECT] block passes validation.
let blck =
block:
let forkedBlock = dag.getForkedBlock(BlockId(
root: envelope.beacon_block_root, slot: envelope.slot)).valueOr:
return dag.checkedReject("ExecutionPayload: invalid block")
withBlck(forkedBlock):
when consensusFork >= ConsensusFork.Gloas:
forkyBlck.asSigned().message
else:
return dag.checkedReject("ExecutionPayload: invalid fork")

# [REJECT] block.slot equals envelope.slot.
if blck.slot != envelope.slot:
return dag.checkedReject("ExecutionPayload: slot mismatch")

template bid: untyped = blck.body.signed_execution_payload_bid.message

# [REJECT] envelope.builder_index == bid.builder_index
if envelope.builder_index != bid.builder_index:
return dag.checkedReject("ExecutionPayload: builder index mismatch")

# [REJECT] payload.block_hash == bid.block_hash
if envelope.payload.block_hash != bid.block_hash:
return dag.checkedReject("ExecutionPayload: block hash mismatch")

# [REJECT] signed_execution_payload_envelope.signature is valid with respect
# to the builder's public key.
withState(dag.headState):
when consensusFork >= ConsensusFork.Gloas:
if not verify_execution_payload_envelope_signature(
dag.forkAtEpoch(envelope.slot.epoch),
getStateField(dag.headState, genesis_validators_root),
envelope.slot.epoch,
signed_execution_payload_envelope.message,
dag.validatorKey(envelope.builder_index).get(),
signed_execution_payload_envelope.signature):
return dag.checkedReject("ExecutionPayload: invalid builder signature")
else:
return dag.checkedReject("ExecutionPayload: invalid fork")

ok()

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/deneb/p2p-interface.md#beacon_aggregate_and_proof
proc validateAttestation*(
Expand Down
25 changes: 25 additions & 0 deletions beacon_chain/spec/datatypes/gloas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,24 @@ type
blob_kzg_commitments*: KzgCommitments
state_root*: Eth2Digest

TrustedExecutionPayloadEnvelope* = object
payload*: deneb.ExecutionPayload
execution_requests*: ExecutionRequests
builder_index*: uint64
beacon_block_root*: Eth2Digest
slot*: Slot
blob_kzg_commitments*: KzgCommitments
state_root*: Eth2Digest

# https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.6/specs/gloas/beacon-chain.md#signedexecutionpayloadenvelope
SignedExecutionPayloadEnvelope* = object
message*: ExecutionPayloadEnvelope
signature*: ValidatorSig

TrustedSignedExecutionPayloadEnvelope* = object
message*: TrustedExecutionPayloadEnvelope
signature*: ValidatorSig

# https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.6/specs/gloas/beacon-chain.md#payloadattestationdata
PayloadAttestationData* = object
beacon_block_root*: Eth2Digest
Expand Down Expand Up @@ -616,11 +629,23 @@ func shortLog*(v: ExecutionPayloadBid): auto =
blob_kzg_commitments_root: shortLog(v.blob_kzg_commitments_root),
)

func shortLog*(v: ExecutionPayloadEnvelope): auto =
(
beacon_block_root: shortLog(v.beacon_block_root),
slot: v.slot,
builder_index: v.builder_index,
state_root: shortLog(v.state_root)
)

template asSigned*(
x: SigVerifiedSignedBeaconBlock |
TrustedSignedBeaconBlock): SignedBeaconBlock =
isomorphicCast[SignedBeaconBlock](x)

template asSigned*(
x: TrustedSignedExecutionPayloadEnvelope): SignedExecutionPayloadEnvelope =
isomorphicCast[SignedExecutionPayloadEnvelope](x)

template asSigVerified*(
x: SignedBeaconBlock |
TrustedSignedBeaconBlock): SigVerifiedSignedBeaconBlock =
Expand Down
Loading
Loading