- 
                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 20 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?