-
Notifications
You must be signed in to change notification settings - Fork 296
Create execution payload gossip validation #7623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from 19 commits
2b61c86
d49413a
411deae
48075c3
46ad77e
8bdb4f8
1286ae7
4c02aa0
4a81e7b
6463ef4
f9295bd
85cd2b6
dafc65a
16a1c56
a4e16b6
a2af12a
d3f1aa2
8fce3e3
7950b9b
9abc8db
8b78858
2f4d295
4922fb1
59fab8f
832e990
7f13385
b96fda8
b0fa3a5
3dbd874
07bf838
a40ff27
1c440d7
c0da29d
b6058b6
cf8fb6e
5d5b8c5
df1600e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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] | ||
| ## 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*( | ||
|
||
| 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*( | ||
|
||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -131,6 +131,7 @@ type | |
| validatorPool*: ref ValidatorPool | ||
| syncCommitteeMsgPool: ref SyncCommitteeMsgPool | ||
| lightClientPool: ref LightClientPool | ||
| fullBlockPool: ref FullBlockPool | ||
|
|
||
| doppelgangerDetection*: DoppelgangerProtection | ||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -197,6 +199,7 @@ proc new*(T: type Eth2Processor, | |
| validatorPool: validatorPool, | ||
| syncCommitteeMsgPool: syncCommitteeMsgPool, | ||
| lightClientPool: lightClientPool, | ||
| fullBlockPool: fullBlockPool, | ||
| quarantine: quarantine, | ||
| blobQuarantine: blobQuarantine, | ||
| dataColumnQuarantine: dataColumnQuarantine, | ||
|
|
@@ -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( | ||
|
||
| 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, | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
tersec marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 = | ||
|
|
||
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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?