From 76176e914e25d337a9c4a99906119719684850bb Mon Sep 17 00:00:00 2001 From: Caleb Omoniyi Date: Sun, 8 Dec 2024 23:15:13 +0000 Subject: [PATCH 1/4] Initial commit for eip-7732 on fulu This commit introduces changes to support EIP-7732 in the Fulu fork: - Execution payloads are removed from the beacon block structure. - Slot intervals are increased to 4 to align with EIP-7732 timing changes. - Refactored validators and consensus object pools for compatibility. --- beacon_chain/beacon_chain_db_immutable.nim | 61 +--- .../blob_quarantine.nim | 43 ++- .../consensus_object_pools/block_dag.nim | 17 +- .../consensus_object_pools/blockchain_dag.nim | 8 +- .../blockchain_list.nim | 3 +- .../data_column_quarantine.nim | 4 +- beacon_chain/el/el_manager.nim | 7 +- beacon_chain/el/engine_api_conversions.nim | 94 ++++-- .../gossip_processing/block_processor.nim | 88 ++++-- beacon_chain/spec/beacon_time.nim | 6 +- beacon_chain/spec/beaconstate.nim | 194 +++++-------- beacon_chain/spec/datatypes/constants.nim | 6 +- beacon_chain/spec/datatypes/fulu.nim | 255 ++++++++-------- beacon_chain/spec/eip7594_helpers.nim | 5 +- beacon_chain/spec/eip7732_helpers.nim | 188 ++++++++++++ .../eth2_apis/eth2_rest_serialization.nim | 5 + beacon_chain/spec/forks_light_client.nim | 84 ++++-- beacon_chain/spec/helpers.nim | 29 +- beacon_chain/spec/helpers_el.nim | 39 ++- beacon_chain/spec/mev/fulu_mev.nim | 56 ++-- beacon_chain/spec/network.nim | 2 +- beacon_chain/spec/signatures.nim | 90 ++++++ beacon_chain/spec/state_transition.nim | 68 +++-- beacon_chain/spec/state_transition_block.nim | 273 +++++++++++++++++- beacon_chain/sync/request_manager.nim | 5 +- beacon_chain/sync/sync_manager.nim | 5 +- beacon_chain/validators/beacon_validators.nim | 5 +- tests/test_sync_manager.nim | 4 +- tests/test_toblindedblock.nim | 132 +++++---- 29 files changed, 1216 insertions(+), 560 deletions(-) create mode 100644 beacon_chain/spec/eip7732_helpers.nim diff --git a/beacon_chain/beacon_chain_db_immutable.nim b/beacon_chain/beacon_chain_db_immutable.nim index d51d9f8bc9..bcf2b1caa1 100644 --- a/beacon_chain/beacon_chain_db_immutable.nim +++ b/beacon_chain/beacon_chain_db_immutable.nim @@ -427,78 +427,47 @@ type genesis_validators_root*: Eth2Digest slot*: Slot fork*: Fork - - # History latest_block_header*: BeaconBlockHeader - ## `latest_block_header.state_root == ZERO_HASH` temporarily - block_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest] - ## Needed to process attestations, older to newer - state_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest] historical_roots*: HashList[Eth2Digest, Limit HISTORICAL_ROOTS_LIMIT] - ## Frozen in Capella, replaced by historical_summaries - - # Eth1 eth1_data*: Eth1Data eth1_data_votes*: HashList[Eth1Data, Limit(EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)] eth1_deposit_index*: uint64 - - # Registry - validators*: - HashList[ValidatorStatusCapella, Limit VALIDATOR_REGISTRY_LIMIT] + validators*: HashList[Validator, Limit VALIDATOR_REGISTRY_LIMIT] balances*: HashList[Gwei, Limit VALIDATOR_REGISTRY_LIMIT] - - # Randomness randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest] - - # Slashings slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, Gwei] - ## Per-epoch sums of slashed effective balances - - # Participation previous_epoch_participation*: EpochParticipationFlags current_epoch_participation*: EpochParticipationFlags - - # Finality justification_bits*: JustificationBits - ## Bit set for every recent justified epoch - previous_justified_checkpoint*: Checkpoint current_justified_checkpoint*: Checkpoint finalized_checkpoint*: Checkpoint - - # Inactivity - inactivity_scores*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT] - - # Light client sync committees + inactivity_scores*: InactivityScores current_sync_committee*: SyncCommittee next_sync_committee*: SyncCommittee - - # Execution latest_execution_payload_header*: fulu.ExecutionPayloadHeader - - # Withdrawals next_withdrawal_index*: WithdrawalIndex next_withdrawal_validator_index*: uint64 - - # Deep history valid from Capella onwards historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT] - deposit_requests_start_index*: uint64 # [New in Electra:EIP6110] - deposit_balance_to_consume*: Gwei # [New in Electra:EIP7251] - exit_balance_to_consume*: Gwei # [New in Electra:EIP7251] - earliest_exit_epoch*: Epoch # [New in Electra:EIP7251] - consolidation_balance_to_consume*: Gwei # [New in Electra:EIP7251] - earliest_consolidation_epoch*: Epoch # [New in Electra:EIP7251] - pending_deposits*: HashList[PendingDeposit, Limit PENDING_DEPOSITS_LIMIT] - ## [New in Electra:EIP7251] - - # [New in Electra:EIP7251] + deposit_requests_start_index*: uint64 + deposit_balance_to_consume*: Gwei + exit_balance_to_consume*: Gwei + earliest_exit_epoch*: Epoch + consolidation_balance_to_consume*: Gwei + earliest_consolidation_epoch*: Epoch + pending_deposits*: + HashList[PendingDeposit, Limit PENDING_DEPOSITS_LIMIT] pending_partial_withdrawals*: HashList[PendingPartialWithdrawal, Limit PENDING_PARTIAL_WITHDRAWALS_LIMIT] pending_consolidations*: HashList[PendingConsolidation, Limit PENDING_CONSOLIDATIONS_LIMIT] - ## [New in Electra:EIP7251] + + # [New in ePBS:eip7732] + latest_block_hash*: Eth2Digest + latest_full_slot*: Slot + latest_withdrawals_root*: Eth2Digest diff --git a/beacon_chain/consensus_object_pools/blob_quarantine.nim b/beacon_chain/consensus_object_pools/blob_quarantine.nim index 7bbffd11b6..f99aa6f169 100644 --- a/beacon_chain/consensus_object_pools/blob_quarantine.nim +++ b/beacon_chain/consensus_object_pools/blob_quarantine.nim @@ -73,13 +73,17 @@ func hasBlob*( func popBlobs*( quarantine: var BlobQuarantine, digest: Eth2Digest, blck: deneb.SignedBeaconBlock | electra.SignedBeaconBlock | - fulu.SignedBeaconBlock): - seq[ref BlobSidecar] = + fulu.SignedBeaconBlock): seq[ref BlobSidecar] = var r: seq[ref BlobSidecar] = @[] - for idx, kzg_commitment in blck.message.body.blob_kzg_commitments: - var b: ref BlobSidecar - if quarantine.blobs.pop((digest, BlobIndex idx, kzg_commitment), b): - r.add(b) + + # Handle `fulu` blocks: skip processing blobs + when typeof(blck).kind == ConsensusFork.Fulu: + return r + else: + for idx, kzg_commitment in blck.message.body.blob_kzg_commitments: + var b: ref BlobSidecar + if quarantine.blobs.pop((digest, BlobIndex idx, kzg_commitment), b): + r.add(b) r func hasBlobs*(quarantine: BlobQuarantine, @@ -87,21 +91,28 @@ func hasBlobs*(quarantine: BlobQuarantine, fulu.SignedBeaconBlock): bool = # Having a fulu SignedBeaconBlock is incorrect atm, but # shall be fixed once data columns are rebased to fulu - for idx, kzg_commitment in blck.message.body.blob_kzg_commitments: - if (blck.root, BlobIndex idx, kzg_commitment) notin quarantine.blobs: - return false + when typeof(blck).kind == ConsensusFork.Fulu: + return false + else: + for idx, kzg_commitment in blck.message.body.blob_kzg_commitments: + if (blck.root, BlobIndex idx, kzg_commitment) notin quarantine.blobs: + return false true func blobFetchRecord*(quarantine: BlobQuarantine, blck: deneb.SignedBeaconBlock | electra.SignedBeaconBlock | fulu.SignedBeaconBlock): BlobFetchRecord = - var indices: seq[BlobIndex] - for i in 0..= ConsensusFork.Bellatrix: + when consensusFork >= ConsensusFork.Fulu: + return Opt.none(Eth2Digest) + elif consensusFork >= ConsensusFork.Bellatrix: if forkyBlck.message.is_execution_block: var mix = eth2digest(forkyBlck.message.body.randao_reveal.toRaw()) mix.data.mxor forkyBlck.message.body.execution_payload.prev_randao.data @@ -2311,7 +2313,9 @@ proc loadExecutionBlockHash*( return Opt.none(Eth2Digest) withBlck(blockData): - when consensusFork >= ConsensusFork.Bellatrix: + when consensusFork >= ConsensusFork.Fulu: + Opt.some ZERO_HASH + elif consensusFork >= ConsensusFork.Bellatrix: Opt.some forkyBlck.message.body.execution_payload.block_hash else: Opt.some ZERO_HASH diff --git a/beacon_chain/consensus_object_pools/blockchain_list.nim b/beacon_chain/consensus_object_pools/blockchain_list.nim index b7677f8b0b..95a38249ec 100644 --- a/beacon_chain/consensus_object_pools/blockchain_list.nim +++ b/beacon_chain/consensus_object_pools/blockchain_list.nim @@ -142,7 +142,8 @@ proc store*(clist: ChainListRef, signedBlock: ForkedSignedBeaconBlock, proc checkBlobs(signedBlock: ForkedSignedBeaconBlock, blobsOpt: Opt[BlobSidecars]): Result[void, VerifierError] = withBlck(signedBlock): - when consensusFork >= ConsensusFork.Deneb: + when consensusFork >= ConsensusFork.Deneb and + consensusFork != ConsensusFork.Fulu: if blobsOpt.isSome(): let blobs = blobsOpt.get() diff --git a/beacon_chain/consensus_object_pools/data_column_quarantine.nim b/beacon_chain/consensus_object_pools/data_column_quarantine.nim index e073f10740..20e9ed7846 100644 --- a/beacon_chain/consensus_object_pools/data_column_quarantine.nim +++ b/beacon_chain/consensus_object_pools/data_column_quarantine.nim @@ -37,8 +37,8 @@ type func init*(T: type DataColumnQuarantine): T = T() -func shortLog*(x: seq[DataColumnFetchRecord]): string = - "[" & x.mapIt(shortLog(it.block_root) & shortLog(it.indices)).join(", ") & "]" +# func shortLog*(x: seq[DataColumnFetchRecord]): string = +# "[" & x.mapIt(shortLog(it.block_root) & shortLog(it.indices.mapIt($it).join(", "))).join(", ") & "]" func put*(quarantine: var DataColumnQuarantine, dataColumnSidecar: ref DataColumnSidecar) = diff --git a/beacon_chain/el/el_manager.nim b/beacon_chain/el/el_manager.nim index 5b6884d5de..3f18b2e9e8 100644 --- a/beacon_chain/el/el_manager.nim +++ b/beacon_chain/el/el_manager.nim @@ -993,7 +993,12 @@ proc sendNewPayload*( let requests = m.elConnections.mapIt: let req = - when typeof(blck).kind >= ConsensusFork.Electra: + when typeof(blck).kind == ConsensusFork.Fulu: + let versioned_hashes: seq[VersionedHash] = @[] + sendNewPayloadToSingleEL( + it, payload, versioned_hashes, + FixedBytes[32] blck.parent_root.data) + elif typeof(blck).kind >= ConsensusFork.Electra: # https://github.com/ethereum/execution-apis/blob/4140e528360fea53c34a766d86a000c6c039100e/src/engine/prague.md#engine_newpayloadv4 let versioned_hashes = mapIt( blck.body.blob_kzg_commitments, diff --git a/beacon_chain/el/engine_api_conversions.nim b/beacon_chain/el/engine_api_conversions.nim index 39d5bc51f9..1e7a9de05f 100644 --- a/beacon_chain/el/engine_api_conversions.nim +++ b/beacon_chain/el/engine_api_conversions.nim @@ -10,6 +10,7 @@ import kzg4844/[kzg_abi, kzg], ../spec/datatypes/[bellatrix, capella, deneb, electra, fulu], + ../spec/forks, web3/[engine_api, engine_api_types] from std/sequtils import mapIt @@ -300,30 +301,77 @@ func asEngineExecutionPayload*(blockBody: capella.BeaconBlockBody): withdrawals: mapIt(executionPayload.withdrawals, it.toEngineWithdrawal)) func asEngineExecutionPayload*( - blockBody: deneb.BeaconBlockBody | electra.BeaconBlockBody | - fulu.BeaconBlockBody): - ExecutionPayloadV3 = - template executionPayload(): untyped = blockBody.execution_payload + blockBody: deneb.BeaconBlockBody | electra.BeaconBlockBody | fulu.BeaconBlockBody +): ExecutionPayloadV3 = + let executionPayload = + when typeof(blockBody).kind == ConsensusFork.Electra: + blockBody.execution_payload + elif typeof(blockBody).kind == ConsensusFork.Deneb: + blockBody.execution_payload + elif typeof(blockBody).kind == ConsensusFork.Fulu: + blockBody.signed_execution_payload_header.message + else: + raise newException(ValueError, "Unsupported BeaconBlockBody type") template getTypedTransaction(tt: bellatrix.Transaction): TypedTransaction = TypedTransaction(tt.distinctBase) - engine_api.ExecutionPayloadV3( - parentHash: executionPayload.parent_hash.asBlockHash, - feeRecipient: Address(executionPayload.fee_recipient.data), - stateRoot: executionPayload.state_root.asBlockHash, - receiptsRoot: executionPayload.receipts_root.asBlockHash, - logsBloom: - FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data), - prevRandao: executionPayload.prev_randao.data.to(Bytes32), - blockNumber: Quantity(executionPayload.block_number), - gasLimit: Quantity(executionPayload.gas_limit), - gasUsed: Quantity(executionPayload.gas_used), - timestamp: Quantity(executionPayload.timestamp), - extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data), - baseFeePerGas: executionPayload.base_fee_per_gas, - blockHash: executionPayload.block_hash.asBlockHash, - transactions: mapIt(executionPayload.transactions, it.getTypedTransaction), - withdrawals: mapIt(executionPayload.withdrawals, it.asEngineWithdrawal), - blobGasUsed: Quantity(executionPayload.blob_gas_used), - excessBlobGas: Quantity(executionPayload.excess_blob_gas)) + when typeof(blockBody).kind == ConsensusFork.Electra: + result = engine_api.ExecutionPayloadV3( + parentHash: executionPayload.parent_hash.asBlockHash, + feeRecipient: Address(executionPayload.fee_recipient.data), + stateRoot: executionPayload.state_root.asBlockHash, + receiptsRoot: executionPayload.receipts_root.asBlockHash, + logsBloom: FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data), + prevRandao: executionPayload.prev_randao.data.to(Bytes32), + blockNumber: Quantity(executionPayload.block_number), + gasLimit: Quantity(executionPayload.gas_limit), + gasUsed: Quantity(executionPayload.gas_used), + timestamp: Quantity(executionPayload.timestamp), + extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data), + baseFeePerGas: executionPayload.base_fee_per_gas, + blockHash: executionPayload.block_hash.asBlockHash, + transactions: mapIt(executionPayload.transactions, it.getTypedTransaction), + withdrawals: mapIt(executionPayload.withdrawals, it.asEngineWithdrawal), + blobGasUsed: Quantity(executionPayload.blob_gas_used), + excessBlobGas: Quantity(executionPayload.excess_blob_gas)) + elif typeof(blockBody).kind == ConsensusFork.Deneb: + result = engine_api.ExecutionPayloadV3( + parentHash: executionPayload.parent_hash.asBlockHash, + feeRecipient: Address(executionPayload.fee_recipient.data), + stateRoot: executionPayload.state_root.asBlockHash, + receiptsRoot: executionPayload.receipts_root.asBlockHash, + logsBloom: FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data), + prevRandao: executionPayload.prev_randao.data.to(Bytes32), + blockNumber: Quantity(executionPayload.block_number), + gasLimit: Quantity(executionPayload.gas_limit), + gasUsed: Quantity(executionPayload.gas_used), + timestamp: Quantity(executionPayload.timestamp), + extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data), + baseFeePerGas: executionPayload.base_fee_per_gas, + blockHash: executionPayload.block_hash.asBlockHash, + transactions: mapIt(executionPayload.transactions, it.getTypedTransaction), + withdrawals: mapIt(executionPayload.withdrawals, it.asEngineWithdrawal), + blobGasUsed: Quantity(executionPayload.blob_gas_used), + excessBlobGas: Quantity(executionPayload.excess_blob_gas)) + elif typeof(blockBody).kind == ConsensusFork.Fulu: + result = engine_api.ExecutionPayloadV3( + parentHash: executionPayload.parent_block_hash.asBlockHash, + feeRecipient: default(Address), + stateRoot: executionPayload.parent_block_hash.asBlockHash, #[TODO]# + receiptsRoot: executionPayload.parent_block_hash.asBlockHash, #[TODO]# + logsBloom: default(FixedBytes[BYTES_PER_LOGS_BLOOM]), + prevRandao: default(FixedBytes[32]), + blockNumber: Quantity(0), + gasLimit: Quantity(executionPayload.gas_limit), + gasUsed: Quantity(0), + timestamp: Quantity(executionPayload.slot), + extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](@[]), + baseFeePerGas: default(UInt256), + blockHash: executionPayload.block_hash.asBlockHash, + transactions: @[], + withdrawals: @[], + blobGasUsed: Quantity(0), + excessBlobGas: Quantity(0)) + else: + raise newException(ValueError, "Unsupported BeaconBlockBody type") \ No newline at end of file diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim index c8a009c7e5..4c93339cc7 100644 --- a/beacon_chain/gossip_processing/block_processor.nim +++ b/beacon_chain/gossip_processing/block_processor.nim @@ -184,19 +184,25 @@ proc storeBackfillBlock( when typeof(signedBlock).kind >= ConsensusFork.Deneb: if blobsOpt.isSome: let blobs = blobsOpt.get() - let kzgCommits = signedBlock.message.body.blob_kzg_commitments.asSeq - if blobs.len > 0 or kzgCommits.len > 0: - let r = validate_blobs(kzgCommits, blobs.mapIt(KzgBlob(bytes: it.blob)), - blobs.mapIt(it.kzg_proof)) - if r.isErr(): - debug "backfill blob validation failed", - blockRoot = shortLog(signedBlock.root), - blobs = shortLog(blobs), - blck = shortLog(signedBlock.message), - kzgCommits = mapIt(kzgCommits, shortLog(it)), - signature = shortLog(signedBlock.signature), - msg = r.error() - blobsOk = r.isOk() + + # Conditionally handle blob_kzg_commitments based on fork + when typeof(signedBlock).kind < ConsensusFork.Fulu: + let kzgCommits = signedBlock.message.body.blob_kzg_commitments.asSeq + if blobs.len > 0 or kzgCommits.len > 0: + let r = validate_blobs(kzgCommits, blobs.mapIt(KzgBlob(bytes: it.blob)), + blobs.mapIt(it.kzg_proof)) + if r.isErr(): + debug "backfill blob validation failed", + blockRoot = shortLog(signedBlock.root), + blobs = shortLog(blobs), + blck = shortLog(signedBlock.message), + kzgCommits = mapIt(kzgCommits, shortLog(it)), + signature = shortLog(signedBlock.signature), + msg = r.error() + blobsOk = r.isOk() + else: + # For Fulu fork, skip blob_kzg_commitments validation + blobsOk = true if not blobsOk: return err(VerifierError.Invalid) @@ -249,7 +255,10 @@ proc expectValidForkchoiceUpdated( deadlineObj = deadlineObj, maxRetriesCount = maxRetriesCount) receivedExecutionBlockHash = - when typeof(receivedBlock).kind >= ConsensusFork.Bellatrix: + when typeof(receivedBlock).kind == ConsensusFork.Fulu: + debugFuluComment "Execution payload removed for Fulu" + (static(default(Eth2Digest))) + elif typeof(receivedBlock).kind >= ConsensusFork.Bellatrix: receivedBlock.message.body.execution_payload.block_hash else: # https://github.com/nim-lang/Nim/issues/19802 @@ -288,15 +297,19 @@ proc newExecutionPayload*( maxRetriesCount: int ): Future[Opt[PayloadExecutionStatus]] {.async: (raises: [CancelledError]).} = - template executionPayload: untyped = blck.body.execution_payload + template executionPayload: untyped = + when compiles(blck.body.execution_payload): + blck.body.execution_payload + else: + debug "Block body does not support execution payload" + nil if not elManager.hasProperlyConfiguredConnection: if elManager.hasConnection: info "No execution client connected; cannot process block payloads", executionPayload = shortLog(executionPayload) else: - debug "No execution client connected; cannot process block payloads", - executionPayload = shortLog(executionPayload) + debug "No execution client connected; cannot process block payloads" return Opt.none PayloadExecutionStatus debug "newPayload: inserting block into execution engine", @@ -307,18 +320,18 @@ proc newExecutionPayload*( await elManager.sendNewPayload(blck, deadlineObj, maxRetriesCount) debug "newPayload: succeeded", - parentHash = executionPayload.parent_hash, - blockHash = executionPayload.block_hash, - blockNumber = executionPayload.block_number, + # parentHash = executionPayload.parent_hash, + # blockHash = executionPayload.block_hash, + # blockNumber = executionPayload.block_number, payloadStatus = $payloadStatus return Opt.some payloadStatus except CatchableError as err: warn "newPayload failed - check execution client", - msg = err.msg, - parentHash = shortLog(executionPayload.parent_hash), - blockHash = shortLog(executionPayload.block_hash), - blockNumber = executionPayload.block_number + msg = err.msg + # parentHash = shortLog(executionPayload.parent_hash), + # blockHash = shortLog(executionPayload.block_hash), + # blockNumber = executionPayload.block_number return Opt.none PayloadExecutionStatus proc newExecutionPayload*( @@ -356,7 +369,7 @@ proc getExecutionValidity( # while for the latter, spam is limited by the request manager. info "execution payload invalid from EL client newPayload", executionPayloadStatus = $executionPayloadStatus.get, - executionPayload = shortLog(blck.message.body.execution_payload), + # executionPayload = shortLog(blck.message.body.execution_payload), blck = shortLog(blck) return NewPayloadStatus.invalid of PayloadExecutionStatus.syncing, PayloadExecutionStatus.accepted: @@ -366,7 +379,7 @@ proc getExecutionValidity( except CatchableError as err: error "newPayload failed and leaked exception", err = err.msg, - executionPayload = shortLog(blck.message.body.execution_payload), + # executionPayload = shortLog(blck.message.body.execution_payload), blck = shortLog(blck) return NewPayloadStatus.noResponse @@ -538,7 +551,9 @@ proc storeBlock( var blobsOk = true let blobs = withBlck(parentBlck.get()): - when consensusFork >= ConsensusFork.Deneb: + when consensusFork == ConsensusFork.Fulu: + Opt.none BlobSidecars + elif consensusFork >= ConsensusFork.Deneb: var blob_sidecars: BlobSidecars for i in 0 ..< forkyBlck.message.body.blob_kzg_commitments.len: let blob = BlobSidecar.new() @@ -570,7 +585,9 @@ proc storeBlock( # progress in its own sync. NewPayloadStatus.noResponse else: - when typeof(signedBlock).kind >= ConsensusFork.Bellatrix: + when typeof(signedBlock).kind == ConsensusFork.Fulu: + NewPayloadStatus.valid + elif typeof(signedBlock).kind >= ConsensusFork.Bellatrix: await self.consensusManager.elManager.getExecutionValidity( signedBlock, deadlineObj, getRetriesCount()) else: @@ -587,7 +604,10 @@ proc storeBlock( # required checks on the CL instead and proceed as if the EL was syncing # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/bellatrix/beacon-chain.md#verify_and_notify_new_payload # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/beacon-chain.md#modified-verify_and_notify_new_payload - when typeof(signedBlock).kind >= ConsensusFork.Bellatrix: + when typeof(signedBlock).kind == ConsensusFork.Fulu: + debugFuluComment "Payload validation skipped for Fulu fork" + discard + elif typeof(signedBlock).kind >= ConsensusFork.Bellatrix: if signedBlock.message.is_execution_block: template payload(): auto = signedBlock.message.body.execution_payload @@ -625,7 +645,10 @@ proc storeBlock( # TODO with v1.4.0, not sure this is still relevant # Establish blob viability before calling addHeadBlock to avoid # writing the block in case of blob error. - when typeof(signedBlock).kind >= ConsensusFork.Deneb: + when typeof(signedBlock).kind == ConsensusFork.Fulu: + debugFuluComment "No need to establish blob validity in Beacon block for Fulu" + discard + elif typeof(signedBlock).kind >= ConsensusFork.Deneb: if blobsOpt.isSome: let blobs = blobsOpt.get() let kzgCommits = signedBlock.message.body.blob_kzg_commitments.asSeq @@ -831,6 +854,9 @@ proc storeBlock( when typeof(forkyBlck).kind < ConsensusFork.Deneb: self[].enqueueBlock( MsgSource.gossip, quarantined, Opt.none(BlobSidecars)) + elif typeof(forkyBlck).kind == ConsensusFork.Fulu: + self[].enqueueBlock( + MsgSource.gossip, quarantined, Opt.none(BlobSidecars)) else: if len(forkyBlck.message.body.blob_kzg_commitments) == 0: self[].enqueueBlock( @@ -926,4 +952,4 @@ proc runQueueProcessingLoop*(self: ref BlockProcessor) {.async.} = discard await idleAsync().withTimeout(idleTimeout) - await self.processBlock(await self[].blockQueue.popFirst()) + await self.processBlock(await self[].blockQueue.popFirst()) \ No newline at end of file diff --git a/beacon_chain/spec/beacon_time.nim b/beacon_chain/spec/beacon_time.nim index 99c6ee5164..aac8082ab7 100644 --- a/beacon_chain/spec/beacon_time.nim +++ b/beacon_chain/spec/beacon_time.nim @@ -43,8 +43,8 @@ const GENESIS_SLOT* = Slot(0) GENESIS_EPOCH* = Epoch(0) # compute_epoch_at_slot(GENESIS_SLOT) - # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/fork-choice.md#constant - INTERVALS_PER_SLOT* = 3 + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/fork-choice.md#constants + INTERVALS_PER_SLOT* = 4 FAR_FUTURE_BEACON_TIME* = BeaconTime(ns_since_genesis: int64.high()) @@ -133,9 +133,11 @@ template `+`*(a: TimeDiff, b: Duration): TimeDiff = const # Offsets from the start of the slot to when the corresponding message should # be sent + # - At 3 seconds, Honest validators submit attestations, similar to the current process. # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/validator.md#attesting attestationSlotOffset* = TimeDiff(nanoseconds: NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT) + # - At 6 seconds, Aggregators aggregate these attestations # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/validator.md#broadcast-aggregate aggregateSlotOffset* = TimeDiff(nanoseconds: NANOSECONDS_PER_SLOT.int64 * 2 div INTERVALS_PER_SLOT) diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 48c2a7a212..fe10a90fd4 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -86,7 +86,7 @@ func get_validator_from_deposit*( # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.7/specs/electra/beacon-chain.md#deposits func get_validator_from_deposit*( - _: electra.BeaconState | fulu.BeaconState, + _: electra.BeaconState | fulu.BeaconState, pubkey: ValidatorPubKey, withdrawal_credentials: Eth2Digest, amount: Gwei): Validator = var validator = Validator( @@ -185,7 +185,7 @@ func get_state_exit_queue_info*( ExitQueueInfo( exit_queue_epoch: exit_queue_epoch, exit_queue_churn: exit_queue_churn) -func get_state_exit_queue_info*(state: electra.BeaconState | +func get_state_exit_queue_info*(state: electra.BeaconState | fulu.BeaconState): ExitQueueInfo = # Electra initiate_validator_exit doesn't have same quadratic aspect given @@ -381,12 +381,12 @@ func get_whistleblower_reward*( # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/electra/beacon-chain.md#modified-slash_validator func get_whistleblower_reward*( - state: electra.BeaconState | fulu.BeaconState, + state: electra.BeaconState | fulu.BeaconState, validator_effective_balance: Gwei): Gwei = validator_effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#slash_validator -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/beacon-chain.md#modified-slash_validator +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/altair/beacon-chain.md#modified-slash_validator # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/bellatrix/beacon-chain.md#modified-slash_validator func get_proposer_reward(state: ForkyBeaconState, whistleblower_reward: Gwei): Gwei = when state is phase0.BeaconState: @@ -461,7 +461,7 @@ func get_initial_beacon_block*(state: phase0.HashedBeaconState): phase0.TrustedSignedBeaconBlock( message: message, root: hash_tree_root(message)) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/beacon-chain.md#initialize-state-for-pure-altair-testnets-and-test-vectors +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/altair/beacon-chain.md#initialize-state-for-pure-altair-testnets-and-test-vectors func get_initial_beacon_block*(state: altair.HashedBeaconState): altair.TrustedSignedBeaconBlock = # The genesis block is implicitly trusted @@ -835,7 +835,7 @@ func check_attestation_index( Result[CommitteeIndex, cstring] = check_attestation_index(data.index, committees_per_slot) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/beacon-chain.md#get_attestation_participation_flag_indices +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/altair/beacon-chain.md#get_attestation_participation_flag_indices func get_attestation_participation_flag_indices( state: altair.BeaconState | bellatrix.BeaconState | capella.BeaconState, data: AttestationData, inclusion_delay: uint64): set[TimelyFlag] = @@ -924,7 +924,7 @@ func get_total_active_balance*(state: ForkyBeaconState, cache: var StateCache): cache.total_active_balance[epoch] = tab return tab -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/beacon-chain.md#get_base_reward_per_increment +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/altair/beacon-chain.md#get_base_reward_per_increment func get_base_reward_per_increment_sqrt( total_active_balance_sqrt: uint64): Gwei = EFFECTIVE_BALANCE_INCREMENT.Gwei * BASE_REWARD_FACTOR div @@ -982,6 +982,7 @@ proc check_attestation*( ok() +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/electra/beacon-chain.md#modified-process_attestation proc check_attestation*( state: electra.BeaconState | fulu.BeaconState, attestation: electra.Attestation | electra.TrustedAttestation, @@ -1002,15 +1003,32 @@ proc check_attestation*( return err("Electra attestation data index not 0") when on_chain: - var participants_count = 0'u64 - for index in attestation.committee_bits.oneIndices: - if not (index.uint64 < get_committee_count_per_slot( + var committee_offset = 0 + for committee_index in attestation.committee_bits.oneIndices: + if not (committee_index.uint64 < get_committee_count_per_slot( state, data.target.epoch, cache)): return err("attestation wrong committee index len") - participants_count += - get_beacon_committee_len(state, data.slot, index.CommitteeIndex, cache) - - if not (lenu64(attestation.aggregation_bits) == participants_count): + let committee = get_beacon_committee( + state, data.slot, committee_index.CommitteeIndex, cache) + + if attestation.aggregation_bits.len < committee_offset + len(committee): + # This would overflow; see invalid_too_many_committee_bits test case + return err("Electra attestation has too many committee bits") + + # This construction modified slightly from spec version to early-exit and + # not create the actual set, but the result is it uses a flag variable to + # look similar. + var committee_attesters_nonzero = false + for i, attester_index in committee: + if attestation.aggregation_bits[committee_offset + i]: + committee_attesters_nonzero = true + break + if not committee_attesters_nonzero: + return err("Electra attestation committee not present in aggregated bits") + + committee_offset += len(committee) + + if not (len(attestation.aggregation_bits) == committee_offset): return err("attestation wrong aggregation bit length") else: let @@ -1032,7 +1050,7 @@ proc check_attestation*( ok() -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/capella/beacon-chain.md#new-process_bls_to_execution_change +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/capella/beacon-chain.md#new-process_bls_to_execution_change proc check_bls_to_execution_change*( genesisFork: Fork, state: capella.BeaconState | deneb.BeaconState | electra.BeaconState | @@ -1193,7 +1211,7 @@ proc process_attestation*( ok(proposer_reward) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/beacon-chain.md#get_next_sync_committee_indices +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/altair/beacon-chain.md#get_next_sync_committee_indices # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices func get_next_sync_committee_keys( state: altair.BeaconState | bellatrix.BeaconState | capella.BeaconState | @@ -1238,7 +1256,7 @@ func get_next_sync_committee_keys( i += 1'u64 res -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential func has_eth1_withdrawal_credential*(validator: Validator): bool = ## Check if ``validator`` has an 0x01 prefixed "eth1" withdrawal credential. validator.withdrawal_credentials.data[0] == ETH1_ADDRESS_WITHDRAWAL_PREFIX @@ -1249,7 +1267,7 @@ func has_execution_withdrawal_credential*(validator: Validator): bool = has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/capella/beacon-chain.md#is_fully_withdrawable_validator +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/capella/beacon-chain.md#is_fully_withdrawable_validator # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/beacon-chain.md#updated-is_fully_withdrawable_validator func is_fully_withdrawable_validator( fork: static ConsensusFork, validator: Validator, balance: Gwei, @@ -1314,7 +1332,7 @@ func switch_to_compounding_validator*( # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw func get_pending_balance_to_withdraw*( - state: electra.BeaconState | fulu.BeaconState, + state: electra.BeaconState | fulu.BeaconState, validator_index: ValidatorIndex): Gwei = var pending_balance: Gwei for withdrawal in state.pending_partial_withdrawals: @@ -1409,7 +1427,7 @@ func get_expected_withdrawals*( # to cleanly treat the results of get_expected_withdrawals as a seq[Withdrawal] # are valuable enough to make that the default version of this spec function. template get_expected_withdrawals_with_partial_count_aux*( - state: electra.BeaconState | fulu.BeaconState, + state: electra.BeaconState | fulu.BeaconState, epoch: Epoch, fetch_balance: untyped): (seq[Withdrawal], uint64) = doAssert epoch - get_current_epoch(state) in [0'u64, 1'u64] @@ -1417,7 +1435,7 @@ template get_expected_withdrawals_with_partial_count_aux*( var withdrawal_index = state.next_withdrawal_index withdrawals: seq[Withdrawal] = @[] - partial_withdrawals_count: uint64 = 0 + processed_partial_withdrawals_count: uint64 = 0 # [New in Electra:EIP7251] Consume pending partial withdrawals for withdrawal in state.pending_partial_withdrawals: @@ -1459,7 +1477,7 @@ template get_expected_withdrawals_with_partial_count_aux*( withdrawals.add w withdrawal_index += 1 - partial_withdrawals_count += 1 + processed_partial_withdrawals_count += 1 let bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) @@ -1470,7 +1488,14 @@ template get_expected_withdrawals_with_partial_count_aux*( for _ in 0 ..< bound: let validator = state.validators.item(validator_index) - balance = fetch_balance + # [Modified in Electra:EIP7251] + partially_withdrawn_balance = block: + var subtot: Gwei + for withdrawal in withdrawals: + if withdrawal.validator_index == validator_index: + subtot += withdrawal.amount + subtot + balance = fetch_balance - partially_withdrawn_balance if is_fully_withdrawable_validator( typeof(state).kind, validator, balance, epoch): var w = Withdrawal( @@ -1494,7 +1519,7 @@ template get_expected_withdrawals_with_partial_count_aux*( break validator_index = (validator_index + 1) mod num_validators - (withdrawals, partial_withdrawals_count) + (withdrawals, processed_partial_withdrawals_count) template get_expected_withdrawals_with_partial_count*( state: electra.BeaconState | fulu.BeaconState): (seq[Withdrawal], uint64) = @@ -1502,7 +1527,7 @@ template get_expected_withdrawals_with_partial_count*( state, get_current_epoch(state)) do: state.balances.item(validator_index) -func get_expected_withdrawals*(state: electra.BeaconState | fulu.BeaconState): +func get_expected_withdrawals*(state: electra.BeaconState | fulu.BeaconState): seq[Withdrawal] = get_expected_withdrawals_with_partial_count(state)[0] @@ -1973,7 +1998,7 @@ func upgrade_to_capella*(cfg: RuntimeConfig, pre: bellatrix.BeaconState): # historical_summaries initialized to correct default automatically ) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/fork.md#upgrading-the-state +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/deneb/fork.md#upgrading-the-state func upgrade_to_deneb*(cfg: RuntimeConfig, pre: capella.BeaconState): ref deneb.BeaconState = let @@ -2083,17 +2108,13 @@ func upgrade_to_electra*( blob_gas_used: pre.latest_execution_payload_header.blob_gas_used, excess_blob_gas: pre.latest_execution_payload_header.excess_blob_gas) - var max_exit_epoch = FAR_FUTURE_EPOCH + var earliest_exit_epoch = + compute_activation_exit_epoch(get_current_epoch(pre)) for v in pre.validators: if v.exit_epoch != FAR_FUTURE_EPOCH: - max_exit_epoch = - if max_exit_epoch == FAR_FUTURE_EPOCH: - v.exit_epoch - else: - max(max_exit_epoch, v.exit_epoch) - if max_exit_epoch == FAR_FUTURE_EPOCH: - max_exit_epoch = get_current_epoch(pre) - let earliest_exit_epoch = max_exit_epoch + 1 + if v.exit_epoch > earliest_exit_epoch: + earliest_exit_epoch = v.exit_epoch + earliest_exit_epoch += 1 let post = (ref electra.BeaconState)( # Versioning @@ -2206,28 +2227,9 @@ func upgrade_to_electra*( post func upgrade_to_fulu*( - cfg: RuntimeConfig, pre: electra.BeaconState, cache: var StateCache): + cfg: RuntimeConfig, pre: electra.BeaconState, cache: var StateCache): ref fulu.BeaconState = - let - epoch = get_current_epoch(pre) - latest_execution_payload_header = fulu.ExecutionPayloadHeader( - parent_hash: pre.latest_execution_payload_header.parent_hash, - fee_recipient: pre.latest_execution_payload_header.fee_recipient, - state_root: pre.latest_execution_payload_header.state_root, - receipts_root: pre.latest_execution_payload_header.receipts_root, - logs_bloom: pre.latest_execution_payload_header.logs_bloom, - prev_randao: pre.latest_execution_payload_header.prev_randao, - block_number: pre.latest_execution_payload_header.block_number, - gas_limit: pre.latest_execution_payload_header.gas_limit, - gas_used: pre.latest_execution_payload_header.gas_used, - timestamp: pre.latest_execution_payload_header.timestamp, - extra_data: pre.latest_execution_payload_header.extra_data, - base_fee_per_gas: pre.latest_execution_payload_header.base_fee_per_gas, - block_hash: pre.latest_execution_payload_header.block_hash, - transactions_root: pre.latest_execution_payload_header.transactions_root, - withdrawals_root: pre.latest_execution_payload_header.withdrawals_root, - blob_gas_used: pre.latest_execution_payload_header.blob_gas_used, - excess_blob_gas: pre.latest_execution_payload_header.excess_blob_gas) + let epoch = get_current_epoch(pre) var max_exit_epoch = FAR_FUTURE_EPOCH for v in pre.validators: @@ -2241,8 +2243,7 @@ func upgrade_to_fulu*( max_exit_epoch = get_current_epoch(pre) let earliest_exit_epoch = max_exit_epoch + 1 - let post = (ref fulu.BeaconState)( - # Versioning + (ref fulu.BeaconState)( genesis_time: pre.genesis_time, genesis_validators_root: pre.genesis_validators_root, slot: pre.slot, @@ -2251,106 +2252,43 @@ func upgrade_to_fulu*( current_version: cfg.FULU_FORK_VERSION, epoch: epoch ), - - # History latest_block_header: pre.latest_block_header, block_roots: pre.block_roots, state_roots: pre.state_roots, historical_roots: pre.historical_roots, - - # Eth1 eth1_data: pre.eth1_data, eth1_data_votes: pre.eth1_data_votes, eth1_deposit_index: pre.eth1_deposit_index, - - # Registry validators: pre.validators, balances: pre.balances, - - # Randomness randao_mixes: pre.randao_mixes, - - # Slashings slashings: pre.slashings, - - # Participation previous_epoch_participation: pre.previous_epoch_participation, current_epoch_participation: pre.current_epoch_participation, - - # Finality justification_bits: pre.justification_bits, previous_justified_checkpoint: pre.previous_justified_checkpoint, current_justified_checkpoint: pre.current_justified_checkpoint, finalized_checkpoint: pre.finalized_checkpoint, - - # Inactivity inactivity_scores: pre.inactivity_scores, - - # Sync current_sync_committee: pre.current_sync_committee, next_sync_committee: pre.next_sync_committee, - - # Execution-layer - latest_execution_payload_header: latest_execution_payload_header, - - # Withdrawals + latest_execution_payload_header: default(fulu.ExecutionPayloadHeader), next_withdrawal_index: pre.next_withdrawal_index, next_withdrawal_validator_index: pre.next_withdrawal_validator_index, - - # Deep history valid from Capella onwards historical_summaries: pre.historical_summaries, - - # [New in Electra:EIP6110] deposit_requests_start_index: UNSET_DEPOSIT_REQUESTS_START_INDEX, - - # [New in Electra:EIP7251] deposit_balance_to_consume: 0.Gwei, exit_balance_to_consume: 0.Gwei, earliest_exit_epoch: earliest_exit_epoch, consolidation_balance_to_consume: 0.Gwei, - earliest_consolidation_epoch: - compute_activation_exit_epoch(get_current_epoch(pre)) + earliest_consolidation_epoch: compute_activation_exit_epoch(get_current_epoch(pre)), - # pending_balance_deposits, pending_partial_withdrawals, and - # pending_consolidations are default empty lists + # [New in epbs:EIP7732] + latest_block_hash: ZERO_HASH, + latest_full_slot: pre.slot, + latest_withdrawals_root: ZERO_HASH ) - post.exit_balance_to_consume = - get_activation_exit_churn_limit(cfg, post[], cache) - post.consolidation_balance_to_consume = - get_consolidation_churn_limit(cfg, post[], cache) - - # [New in Electra:EIP7251] - # add validators that are not yet active to pending balance deposits - var pre_activation: seq[(Epoch, uint64)] - for index, validator in post.validators: - if validator.activation_epoch == FAR_FUTURE_EPOCH: - pre_activation.add((validator.activation_eligibility_epoch, index.uint64)) - sort(pre_activation) - - for (_, index) in pre_activation: - let balance = post.balances.item(index) - post.balances[index] = 0.Gwei - let validator = addr post.validators.mitem(index) - validator[].effective_balance = 0.Gwei - validator[].activation_eligibility_epoch = FAR_FUTURE_EPOCH - # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder and - # GENESIS_SLOT to distinguish from a pending deposit request - discard post.pending_deposits.add PendingDeposit( - pubkey: validator[].pubkey, - withdrawal_credentials: validator[].withdrawal_credentials, - amount: balance, - signature: ValidatorSig.infinity, - slot: GENESIS_SLOT) - - # Ensure early adopters of compounding credentials go through the activation - # churn - for index, validator in post.validators: - if has_compounding_withdrawal_credential(validator): - queue_excess_active_balance(post[], index.uint64) - - post - func latest_block_root(state: ForkyBeaconState, state_root: Eth2Digest): Eth2Digest = # The root of the last block that was successfully applied to this state - @@ -2474,4 +2412,4 @@ func can_advance_slots( target_slot >= state.data.slot and block_root == state.latest_block_root func can_advance_slots*( state: ForkedHashedBeaconState, block_root: Eth2Digest, target_slot: Slot): bool = - withState(state): forkyState.can_advance_slots(block_root, target_slot) + withState(state): forkyState.can_advance_slots(block_root, target_slot) \ No newline at end of file diff --git a/beacon_chain/spec/datatypes/constants.nim b/beacon_chain/spec/datatypes/constants.nim index 09a62b4265..d96647a069 100644 --- a/beacon_chain/spec/datatypes/constants.nim +++ b/beacon_chain/spec/datatypes/constants.nim @@ -55,6 +55,10 @@ const DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF* = DomainType([byte 0x08, 0x00, 0x00, 0x00]) DOMAIN_CONTRIBUTION_AND_PROOF* = DomainType([byte 0x09, 0x00, 0x00, 0x00]) + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#domain-types + DOMAIN_BEACON_BUILDER* = DomainType([byte 0x1B, 0x00, 0x00, 0x00]) + DOMAIN_PTC_ATTESTER* = DomainType([byte 0x0C, 0x00, 0x00, 0x00]) + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/capella/beacon-chain.md#domain-types DOMAIN_BLS_TO_EXECUTION_CHANGE* = DomainType([byte 0x0a, 0x00, 0x00, 0x00]) @@ -85,4 +89,4 @@ const FULL_EXIT_REQUEST_AMOUNT*: uint64 = 0 # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/electra/beacon-chain.md#withdrawal-prefixes - COMPOUNDING_WITHDRAWAL_PREFIX* = 0x02 + COMPOUNDING_WITHDRAWAL_PREFIX* = 0x02 \ No newline at end of file diff --git a/beacon_chain/spec/datatypes/fulu.nim b/beacon_chain/spec/datatypes/fulu.nim index 260dbeea2c..df385a529a 100644 --- a/beacon_chain/spec/datatypes/fulu.nim +++ b/beacon_chain/spec/datatypes/fulu.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2024 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). @@ -7,7 +7,7 @@ {.push raises: [].} -# Types specific to Fulu (i.e. known to have changed across hard forks) - see +# Types specific to eip-7732 (i.e. known to have changed across hard forks) - see # `base` for types and guidelines common across forks # TODO Careful, not nil analysis is broken / incomplete and the semantics will @@ -16,28 +16,33 @@ {.experimental: "notnil".} import - std/[sequtils, typetraits], - "."/[phase0, base, electra], - chronicles, json_serialization, ssz_serialization/[merkleization, proofs], ssz_serialization/types as sszTypes, + kzg4844/[kzg, kzg_abi], + std/typetraits, + chronicles, ../digest, - kzg4844/[kzg, kzg_abi] + "."/[base, phase0, electra], + std/typetraits -from std/strutils import join from stew/bitops2 import log2trunc -from stew/byteutils import to0xHex +from kzg4844 import KzgCommitment, KzgProof from ./altair import EpochParticipationFlags, InactivityScores, SyncAggregate, SyncCommittee, - TrustedSyncAggregate, SyncnetBits, num_active_participants + TrustedSyncAggregate, num_active_participants, SyncnetBits from ./bellatrix import BloomLogs, ExecutionAddress, Transaction from ./capella import - ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChange, - SignedBLSToExecutionChangeList, Withdrawal, EXECUTION_PAYLOAD_GINDEX + ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChangeList, + Withdrawal, EXECUTION_PAYLOAD_GINDEX from ./deneb import Blobs, BlobsBundle, KzgCommitments, KzgProofs +from ./electra import + PendingDeposit, PendingPartialWithdrawal, + PendingConsolidation, ExecutionPayload, Attestation, TrustedAttestation, + ElectraCommitteeValidatorsBits,AttesterSlashing, AttestationCommitteeBits, + FINALIZED_ROOT_GINDEX_ELECTRA, ExecutionRequests -export json_serialization, base +export json_serialization, base, kzg4844 const # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/_features/eip7594/polynomial-commitments-sampling.md#cells @@ -119,7 +124,51 @@ type syncnets*: SyncnetBits custody_subnet_count*: CscCount - # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/beacon-chain.md#executionpayload + +const + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#misc + PTC_SIZE* = 512 + + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#max-operations-per-block + MAX_PAYLOAD_ATTESTATIONS = 4 +type + + PTCStatus* = distinct uint64 + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payloadattestationdata + PayloadAttestationData* = object + beaconBlockRoot*: Eth2Digest + slot*: Slot + payload_Status*: uint8 + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payloadattestation + PayloadAttestation* = object + aggregation_bits*: ElectraCommitteeValidatorsBits + data*: PayloadAttestationData + signature*: ValidatorSig + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payloadattestationmessage + PayloadAttestationMessage* = object + validatorIndex*: ValidatorIndex + data*: PayloadAttestationData + signature*: ValidatorSig + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#indexedpayloadattestation + IndexedPayloadAttestation* = object + attestingIndices*: List[ValidatorIndex, Limit PTC_SIZE] + data*: PayloadAttestationData + signature*: ValidatorSig + +# https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payload-status +const + PAYLOAD_ABSENT* = PTCStatus(0) + PAYLOAD_PRESENT* = PTCStatus(1) + PAYLOAD_WITHHELD* = PTCStatus(2) + PAYLOAD_INVALID_STATUS* = PTCStatus(3) + +type + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/beacon-chain.md#executionpayload ExecutionPayload* = object # Execution block header fields parent_hash*: Eth2Digest @@ -151,29 +200,19 @@ type blobsBundle*: BlobsBundle executionRequests*: array[3, seq[byte]] - # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/beacon-chain.md#executionpayloadheader + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#executionpayloadheader ExecutionPayloadHeader* = object # Execution block header fields - parent_hash*: Eth2Digest - fee_recipient*: ExecutionAddress - state_root*: Eth2Digest - receipts_root*: Eth2Digest - logs_bloom*: BloomLogs - prev_randao*: Eth2Digest - block_number*: uint64 + parent_block_hash*: Eth2Digest + parent_block_root*: Eth2Digest gas_limit*: uint64 - gas_used*: uint64 - timestamp*: uint64 - extra_data*: List[byte, MAX_EXTRA_DATA_BYTES] - base_fee_per_gas*: UInt256 + builder_index*: uint64 + slot*: Slot + value*: Gwei + blob_kzg_commitments_root*: KzgCommitments # Extra payload fields block_hash*: Eth2Digest - ## Hash of execution block - transactions_root*: Eth2Digest - withdrawals_root*: Eth2Digest - blob_gas_used*: uint64 - excess_blob_gas*: uint64 ExecutePayload* = proc( execution_payload: ExecutionPayload): bool {.gcsafe, raises: [].} @@ -285,88 +324,77 @@ type ## (used to compute safety threshold) current_max_active_participants*: uint64 - # https://github.com/ethereum/consensus-specs/blob/82133085a1295e93394ebdf71df8f2f6e0962588/specs/electra/beacon-chain.md#beaconstate + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#executionpayloadenvelope + SignedExecutionPayloadHeader* = object + message*: ExecutionPayloadHeader + signature*: ValidatorSig + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#signedexecutionpayloadenvelope + ExecutionPayloadEnvelope* = object + payload*: ExecutionPayload + execution_requests*: ExecutionRequests + builder_index*: uint64 + beacon_block_root*: Eth2Digest + blob_kzg_commitments*: List[KzgCommitment, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK] + payload_withheld*: bool + state_root*: Eth2Digest + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#signedexecutionpayloadenvelope + SignedExecutionPayloadEnvelope* = object + message*: ExecutionPayloadEnvelope + signature*: ValidatorSig + + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#beaconstate BeaconState* = object # Versioning genesis_time*: uint64 genesis_validators_root*: Eth2Digest slot*: Slot fork*: Fork - - # History latest_block_header*: BeaconBlockHeader - ## `latest_block_header.state_root == ZERO_HASH` temporarily - block_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest] - ## Needed to process attestations, older to newer - state_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest] historical_roots*: HashList[Eth2Digest, Limit HISTORICAL_ROOTS_LIMIT] - ## Frozen in Capella, replaced by historical_summaries - - # Eth1 eth1_data*: Eth1Data eth1_data_votes*: HashList[Eth1Data, Limit(EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)] eth1_deposit_index*: uint64 - - # Registry validators*: HashList[Validator, Limit VALIDATOR_REGISTRY_LIMIT] balances*: HashList[Gwei, Limit VALIDATOR_REGISTRY_LIMIT] - - # Randomness randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest] - - # Slashings slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, Gwei] - ## Per-epoch sums of slashed effective balances - - # Participation previous_epoch_participation*: EpochParticipationFlags current_epoch_participation*: EpochParticipationFlags - - # Finality justification_bits*: JustificationBits - ## Bit set for every recent justified epoch - previous_justified_checkpoint*: Checkpoint current_justified_checkpoint*: Checkpoint finalized_checkpoint*: Checkpoint - - # Inactivity inactivity_scores*: InactivityScores - - # Light client sync committees current_sync_committee*: SyncCommittee next_sync_committee*: SyncCommittee - - # Execution latest_execution_payload_header*: ExecutionPayloadHeader - ## [Modified in Electra:EIP6110:EIP7002] - - # Withdrawals next_withdrawal_index*: WithdrawalIndex next_withdrawal_validator_index*: uint64 - - # Deep history valid from Capella onwards historical_summaries*: HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT] - deposit_requests_start_index*: uint64 # [New in Electra:EIP6110] - deposit_balance_to_consume*: Gwei # [New in Electra:EIP7251] - exit_balance_to_consume*: Gwei # [New in Electra:EIP7251] - earliest_exit_epoch*: Epoch # [New in Electra:EIP7251] - consolidation_balance_to_consume*: Gwei # [New in Electra:EIP7251] - earliest_consolidation_epoch*: Epoch # [New in Electra:EIP7251] - pending_deposits*: HashList[PendingDeposit, Limit PENDING_DEPOSITS_LIMIT] - ## [New in Electra:EIP7251] - - # [New in Electra:EIP7251] + deposit_requests_start_index*: uint64 + deposit_balance_to_consume*: Gwei + exit_balance_to_consume*: Gwei + earliest_exit_epoch*: Epoch + consolidation_balance_to_consume*: Gwei + earliest_consolidation_epoch*: Epoch + pending_deposits*: + HashList[PendingDeposit, Limit PENDING_DEPOSITS_LIMIT] pending_partial_withdrawals*: HashList[PendingPartialWithdrawal, Limit PENDING_PARTIAL_WITHDRAWALS_LIMIT] pending_consolidations*: HashList[PendingConsolidation, Limit PENDING_CONSOLIDATIONS_LIMIT] - ## [New in Electra:EIP7251] + + # [New in PBS] + latest_block_hash*: Eth2Digest + latest_full_slot*: Slot + latest_withdrawals_root*: Eth2Digest # TODO Careful, not nil analysis is broken / incomplete and the semantics will # likely change in future versions of the language: @@ -379,7 +407,7 @@ type data*: BeaconState root*: Eth2Digest # hash_tree_root(data) - # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/beacon-chain.md#beaconblock + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/phase0/beacon-chain.md#beaconblock BeaconBlock* = object ## For each slot, a proposer is chosen from the validator pool to propose ## a new block. Once the block as been proposed, it is transmitted to @@ -436,7 +464,7 @@ type state_root*: Eth2Digest body*: TrustedBeaconBlockBody - # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.6/specs/electra/beacon-chain.md#beaconblockbody + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#beaconblockbody BeaconBlockBody* = object randao_reveal*: ValidatorSig eth1_data*: Eth1Data @@ -456,15 +484,17 @@ type voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS] sync_aggregate*: SyncAggregate + bls_to_execution_changes*: SignedBLSToExecutionChangeList + + signed_execution_payload_header*: SignedExecutionPayloadHeader + payload_attestations*: List[PayloadAttestation, Limit MAX_PAYLOAD_ATTESTATIONS] # Execution - execution_payload*: fulu.ExecutionPayload # [Modified in Electra:EIP6110:EIP7002] - bls_to_execution_changes*: SignedBLSToExecutionChangeList - blob_kzg_commitments*: KzgCommitments - execution_requests*: ExecutionRequests # [New in Electra] + # execution_payload*: ExecutionPayload # [Removed in epbs] + # blob_kzg_commitments*: KzgCommitments # [Removed in epbs] SigVerifiedBeaconBlockBody* = object - ## A BeaconBlock body with signatures verified + ## An BeaconBlock body with signatures verified ## including: ## - Randao reveal ## - Attestations @@ -498,10 +528,10 @@ type sync_aggregate*: TrustedSyncAggregate # Execution - execution_payload*: ExecutionPayload # [Modified in Electra:EIP6110:EIP7002] bls_to_execution_changes*: SignedBLSToExecutionChangeList - blob_kzg_commitments*: KzgCommitments - execution_requests*: ExecutionRequests # [New in Electra] + signed_execution_payload_header*: SignedExecutionPayloadHeader + payload_attestations*: List[PayloadAttestation, Limit MAX_PAYLOAD_ATTESTATIONS] + TrustedBeaconBlockBody* = object ## A full verified block @@ -526,10 +556,9 @@ type sync_aggregate*: TrustedSyncAggregate # Execution - execution_payload*: ExecutionPayload # [Modified in Electra:EIP6110:EIP7002] bls_to_execution_changes*: SignedBLSToExecutionChangeList - blob_kzg_commitments*: KzgCommitments - execution_requests*: ExecutionRequests # [New in Electra] + signed_execution_payload_header*: SignedExecutionPayloadHeader + payload_attestations*: List[PayloadAttestation, Limit MAX_PAYLOAD_ATTESTATIONS] # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#signedbeaconblock SignedBeaconBlock* = object @@ -569,6 +598,13 @@ type root* {.dontSerialize.}: Eth2Digest # cached root of signed beacon block +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.7/specs/electra/beacon-chain.md#attestation + Attestation* = object + aggregation_bits*: ElectraCommitteeValidatorsBits + data*: AttestationData + signature*: ValidatorSig + committee_bits*: AttestationCommitteeBits # [New in Electra:EIP7549] + SomeSignedBeaconBlock* = SignedBeaconBlock | SigVerifiedSignedBeaconBlock | @@ -588,27 +624,17 @@ type kzg_proofs*: KzgProofs blobs*: Blobs -func shortLog*(v: DataColumnSidecar): auto = - ( - index: v.index, - kzg_commitments: v.kzg_commitments.len, - kzg_proofs: v.kzg_proofs.len, - block_header: shortLog(v.signed_block_header.message), - ) - -func shortLog*(v: seq[DataColumnSidecar]): auto = - "[" & v.mapIt(shortLog(it)).join(", ") & "]" - -func shortLog*(x: seq[DataColumnIdentifier]): string = - "[" & x.mapIt(shortLog(it.block_root) & "/" & $it.index).join(", ") & "]" - -func shortLog*(x: seq[ColumnIndex]): string = - "<" & x.mapIt($it).join(", ") & ">" - # TODO: There should be only a single generic HashedBeaconState definition func initHashedBeaconState*(s: BeaconState): HashedBeaconState = HashedBeaconState(data: s) +func shortLog*(v: PayloadAttestationData): auto = + ( + beaconBlockRoot: shortLog(v.beaconBlockRoot), + slot: shortLog(v.slot), + payload_Status: $v.payload_Status + ) + func shortLog*(v: SomeBeaconBlock): auto = ( slot: shortLog(v.slot), @@ -623,13 +649,14 @@ func shortLog*(v: SomeBeaconBlock): auto = deposits_len: v.body.deposits.len(), voluntary_exits_len: v.body.voluntary_exits.len(), sync_committee_participants: v.body.sync_aggregate.num_active_participants, - block_number: v.body.execution_payload.block_number, - # TODO checksum hex? shortlog? - block_hash: to0xHex(v.body.execution_payload.block_hash.data), - parent_hash: to0xHex(v.body.execution_payload.parent_hash.data), - fee_recipient: to0xHex(v.body.execution_payload.fee_recipient.data), + + # Default values for missing execution_payload fields + block_number: uint64(0), # Default value for block number + block_hash: "N/A", # Default string for missing block hash + parent_hash: "N/A", # Default string for missing parent hash + fee_recipient: "N/A", # Default string for missing fee recipient bls_to_execution_changes_len: v.body.bls_to_execution_changes.len(), - blob_kzg_commitments_len: v.body.blob_kzg_commitments.len(), + blob_kzg_commitments_len: v.body.signed_execution_payload_header.message.blob_kzg_commitments_root.len(), ) func shortLog*(v: SomeSignedBeaconBlock): auto = @@ -654,8 +681,8 @@ func shortLog*(v: ExecutionPayload): auto = block_hash: shortLog(v.block_hash), num_transactions: len(v.transactions), num_withdrawals: len(v.withdrawals), - blob_gas_used: $(v.blob_gas_used), - excess_blob_gas: $(v.excess_blob_gas) + # blob_gas_used: $(v.blob_gas_used), + # excess_blob_gas: $(v.excess_blob_gas) ) template asSigned*( @@ -684,4 +711,4 @@ template asTrusted*( x: SignedBeaconBlock | SigVerifiedSignedBeaconBlock | MsgTrustedSignedBeaconBlock): TrustedSignedBeaconBlock = - isomorphicCast[TrustedSignedBeaconBlock](x) \ No newline at end of file + isomorphicCast[TrustedSignedBeaconBlock](x) diff --git a/beacon_chain/spec/eip7594_helpers.nim b/beacon_chain/spec/eip7594_helpers.nim index 42c1bd8a11..036d258752 100644 --- a/beacon_chain/spec/eip7594_helpers.nim +++ b/beacon_chain/spec/eip7594_helpers.nim @@ -129,13 +129,12 @@ proc compute_matrix*(blobs: seq[KzgBlob]): Result[seq[MatrixEntry], cstring] = ok(extended_matrix) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/_features/eip7594/das-core.md#recover_matrix proc recover_matrix*(partial_matrix: seq[MatrixEntry], - blobCount: int): - Result[seq[MatrixEntry], cstring] = + blobCount: int): Result[seq[MatrixEntry], cstring] = ## This helper demonstrates how to apply recover_cells_and_kzg_proofs ## The data structure for storing cells is implementation-dependent var extended_matrix: seq[MatrixEntry] + for blob_index in 0..= + uint8(PAYLOAD_INVALID_STATUS): + return false + ## Check if ``indexed_attestation`` is not empty, has sorted and unique + ## indices and has a valid aggregate signature. + + template is_sorted_and_unique(s: untyped): bool = + var res = true + for i in 1 ..< s.len: + if s[i - 1].uint64 >= s[i].uint64: + res = false + break + res + + if len(indexed_payload_attestation.attesting_indices) == 0: + return false + + # Check if ``indexed_payload_attestation`` is has sorted and unique + if not is_sorted_and_unique(indexed_payload_attestation.attesting_indices): + return false + + # Verify aggregate signature + let pubkeys = mapIt( + indexed_payload_attestation.attesting_indices, state.validators[it].pubkey) + + let domain = get_domain( + state.fork, DOMAIN_PTC_ATTESTER, GENESIS_EPOCH, + state.genesis_validators_root) + + let signing_root = compute_signing_root( + indexed_payload_attestation.data, domain) + + blsFastAggregateVerify(pubkeys, signing_root.data, + indexed_payload_attestation.signature) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#get_ptc +proc get_ptc*(state: fulu.BeaconState, slot: Slot, cache: var StateCache): seq[ValidatorIndex] = + let + epoch = epoch(slot) + committees_per_slot = bit_floor(min(get_committee_count_per_slot( + state, epoch, cache), PTC_SIZE)) + members_per_committee = (PTC_SIZE div committees_per_slot) + + var validator_indices = newSeq[ValidatorIndex](PTC_SIZE) + + for committee_index in get_committee_indices(committees_per_slot): + let beacon_committee = get_beacon_committee(state, slot, committee_index, cache) + validator_indices.add(beacon_committee[0 ..< members_per_committee]) + + return validator_indices + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#modified-get_attesting_indices +proc get_attesting_indices(state: fulu.BeaconState, + attestation: fulu.Attestation, cache: var StateCache, + cfg: RuntimeConfig): HashSet[ValidatorIndex] = + + var + output: HashSet[ValidatorIndex] + committee_offset = 0 + + for committee_index in get_committee_indices(attestation.committee_bits): + let committee = get_beacon_committee(state, attestation.data.slot, committee_index, cache) + + var committee_attesters: HashSet[ValidatorIndex] + for i, validator_index in committee: + if attestation.aggregation_bits[committee_offset + i]: + committee_attesters.incl(validator_index) + + # Merge the current committee_attesters set with + # the overall output set of attesting validators + output.incl(committee_attesters) + + committee_offset += len(committee) + + if epoch(attestation.data.slot) < cfg.FULU_FORK_EPOCH: + return output + + let ptc = get_ptc(state, attestation.data.slot, cache) + return output - ptc.toHashSet() + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#get_payload_attesting_indices +proc get_payload_attesting_indices(state: fulu.BeaconState, slot: Slot, + payload_attestation: fulu.PayloadAttestation, + cache: var StateCache): List[ValidatorIndex, Limit PTC_SIZE] = + + let ptc = get_ptc(state, slot, cache) + var output: List[ValidatorIndex, Limit PTC_SIZE] + + for i, index in ptc.pairs: + if payload_attestation.aggregation_bits[i]: + discard output.add(index) + + output + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#get_indexed_payload_attestation +proc get_indexed_payload_attestation*(state: var fulu.BeaconState, slot: Slot, + payload_attestation: PayloadAttestation, + cache: var StateCache): IndexedPayloadAttestation = + + let attesting_indices = get_payload_attesting_indices( + state, slot, payload_attestation, cache) + +template sort_and_unique(s: var List[ValidatorIndex, Limit PTC_SIZE] + ): List[ValidatorIndex, Limit PTC_SIZE] = + s.sort() + + var unique_list: List[ValidatorIndex] + + for index in s: + # Check last added element for uniqueness + if unique_list.len == 0 or unique_list[^1] != index: + discard uniqueList.add(index) + unique_list + + IndexedPayloadAttestation( + attesting_indices: sort_and_unique(attesting_indices), + data: payload_attestation.data, + signature: payload_attestation.signature + ) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/validator.md#validator-assignment +proc get_ptc_assignment(state: fulu.BeaconState, epoch: Epoch, cache: var StateCache, + validator_index: ValidatorIndex): Opt[Slot] = + + let + start_slot = start_slot(epoch) + next_epoch = get_current_epoch(state) + 1 + doAssert epoch <= next_epoch + + for slot in start_slot .. start_slot + SLOTS_PER_EPOCH - 1: + if validator_index in get_ptc(state, slot, cache): + return Opt.some slot + return Opt.none(Slot) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#is_parent_block_full +func is_parent_block_full*(state: fulu.BeaconState): bool = + return state.latest_execution_payload_header.block_hash == + state.latest_block_hash + diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 3c02d54b70..e5fde6b128 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -271,6 +271,11 @@ RestJson.useDefaultSerializationFor( fulu.ExecutionPayload, fulu.ExecutionPayloadHeader, fulu.SignedBeaconBlock, + fulu.SignedExecutionPayloadHeader, + fulu.SignedExecutionPayloadEnvelope, + fulu.PayloadAttestation, + fulu.PayloadAttestationData, + fulu.PayloadAttestationMessage, fulu_mev.BlindedBeaconBlock, fulu_mev.BlindedBeaconBlockBody, fulu_mev.BuilderBid, diff --git a/beacon_chain/spec/forks_light_client.nim b/beacon_chain/spec/forks_light_client.nim index 21b590bbb3..e664bcec37 100644 --- a/beacon_chain/spec/forks_light_client.nim +++ b/beacon_chain/spec/forks_light_client.nim @@ -1204,36 +1204,60 @@ func toElectraLightClientHeader( execution_branch: blck.message.body.build_proof( capella.EXECUTION_PAYLOAD_GINDEX).get) -func toElectraLightClientHeader( - # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095 - blck: - electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock | - fulu.SignedBeaconBlock | fulu.TrustedSignedBeaconBlock -): electra.LightClientHeader = - template payload: untyped = blck.message.body.execution_payload - electra.LightClientHeader( - beacon: blck.message.toBeaconBlockHeader(), - execution: electra.ExecutionPayloadHeader( - parent_hash: payload.parent_hash, - fee_recipient: payload.fee_recipient, - state_root: payload.state_root, - receipts_root: payload.receipts_root, - logs_bloom: payload.logs_bloom, - prev_randao: payload.prev_randao, - block_number: payload.block_number, - gas_limit: payload.gas_limit, - gas_used: payload.gas_used, - timestamp: payload.timestamp, - extra_data: payload.extra_data, - base_fee_per_gas: payload.base_fee_per_gas, - block_hash: payload.block_hash, - transactions_root: hash_tree_root(payload.transactions), - withdrawals_root: hash_tree_root(payload.withdrawals), - blob_gas_used: payload.blob_gas_used, - excess_blob_gas: payload.excess_blob_gas), - execution_branch: blck.message.body.build_proof( - capella.EXECUTION_PAYLOAD_GINDEX).get) - +func toElectraLightClientHeader[T](blck: T): electra.LightClientHeader = + when T is electra.SignedBeaconBlock or T is electra.TrustedSignedBeaconBlock: + let payload = blck.message.body.execution_payload + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: electra.ExecutionPayloadHeader( + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: hash_tree_root(payload.transactions), + withdrawals_root: hash_tree_root(payload.withdrawals), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas + ), + execution_branch: blck.message.body.build_proof(capella.EXECUTION_PAYLOAD_GINDEX).get + ) + elif T is fulu.SignedBeaconBlock or T is fulu.TrustedSignedBeaconBlock: + let header = blck.message.body.signed_execution_payload_header.message + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: electra.ExecutionPayloadHeader( + parent_hash: header.parent_block_hash, + # fee_recipient: default(Address), + state_root: Eth2Digest(), + receipts_root: Eth2Digest(), + # logs_bloom: default(LogsBloom), + prev_randao: Eth2Digest(), + block_number: 0, + gas_limit: header.gas_limit, + gas_used: 0, + timestamp: 0, + # extra_data: @[], + # base_fee_per_gas: 0, + block_hash: Eth2Digest(), + transactions_root: Eth2Digest(), + withdrawals_root: Eth2Digest(), + # blob_gas_used: 0, + # excess_blob_gas: 0 + ), + execution_branch: blck.message.body.build_proof(capella.EXECUTION_PAYLOAD_GINDEX).get + ) + else: + raise newException(ValueError, "Unsupported block type for light client header") + func toLightClientHeader*( # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095 blck: diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index d77005b61f..b524b4800c 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -384,13 +384,18 @@ func contextEpoch*(update: SomeForkyLightClientUpdate): Epoch = func is_merge_transition_complete*( state: bellatrix.BeaconState | capella.BeaconState | deneb.BeaconState | electra.BeaconState | fulu.BeaconState): bool = - const defaultExecutionPayloadHeader = - default(typeof(state.latest_execution_payload_header)) - state.latest_execution_payload_header != defaultExecutionPayloadHeader + when typeof(state) is fulu.BeaconState: + true + else: + const defaultExecutionPayloadHeader = + default(typeof(state.latest_execution_payload_header)) + state.latest_execution_payload_header != defaultExecutionPayloadHeader # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/sync/optimistic.md#helpers func is_execution_block*(blck: SomeForkyBeaconBlock): bool = - when typeof(blck).kind >= ConsensusFork.Bellatrix: + when typeof(blck).kind >= ConsensusFork.Fulu: + false # Always return false for Fulu blocks + elif typeof(blck).kind >= ConsensusFork.Bellatrix: const defaultExecutionPayload = default(typeof(blck.body.execution_payload)) blck.body.execution_payload != defaultExecutionPayload @@ -411,9 +416,19 @@ func is_merge_transition_block( electra.SigVerifiedBeaconBlockBody | fulu.BeaconBlockBody | fulu.TrustedBeaconBlockBody | fulu.SigVerifiedBeaconBlockBody): bool = - const defaultExecutionPayload = default(typeof(body.execution_payload)) - not is_merge_transition_complete(state) and - body.execution_payload != defaultExecutionPayload + # Handle Fulu block type differently + when typeof(body) is fulu.BeaconBlockBody or + typeof(body) is fulu.TrustedBeaconBlockBody or + typeof(body) is fulu.SigVerifiedBeaconBlockBody: + # Check if there's a signed execution payload header and it's not default + let defaultHeader = default(typeof(body.signed_execution_payload_header.message)) + not is_merge_transition_complete(state) and + body.signed_execution_payload_header.message != defaultHeader + else: + # For other block types, use existing logic + const defaultExecutionPayload = default(typeof(body.execution_payload)) + not is_merge_transition_complete(state) and + body.execution_payload != defaultExecutionPayload # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/bellatrix/beacon-chain.md#is_execution_enabled func is_execution_enabled*( diff --git a/beacon_chain/spec/helpers_el.nim b/beacon_chain/spec/helpers_el.nim index 9b91cc5e95..5451cd14b0 100644 --- a/beacon_chain/spec/helpers_el.nim +++ b/beacon_chain/spec/helpers_el.nim @@ -21,21 +21,28 @@ func readExecutionTransaction( # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/deneb/beacon-chain.md#is_valid_versioned_hashes func is_valid_versioned_hashes*(blck: ForkyBeaconBlock): Result[void, string] = - static: doAssert typeof(blck).kind >= ConsensusFork.Deneb - template transactions: untyped = blck.body.execution_payload.transactions - template commitments: untyped = blck.body.blob_kzg_commitments + # static: doAssert typeof(blck).kind >= ConsensusFork.Deneb - var i = 0 - for txBytes in transactions: - if txBytes.len == 0 or txBytes[0] != TxEip4844.byte: - continue # Only blob transactions may have blobs - let tx = ? txBytes.readExecutionTransaction() - for vHash in tx.versionedHashes: - if commitments.len <= i: - return err("Extra blobs without matching `blob_kzg_commitments`") - if vHash.data != kzg_commitment_to_versioned_hash(commitments[i]): - return err("Invalid `blob_versioned_hash` at index " & $i) - inc i - if i != commitments.len: - return err("Extra `blob_kzg_commitments` without matching blobs") + when typeof(blck).kind == ConsensusFork.Fulu: + # Return a descriptive error string for Fulu fork + debugFuluComment "Versioned hashes validation not implemented for Fulu fork" + return ok() + else: + static: doAssert typeof(blck).kind >= ConsensusFork.Deneb + template transactions: untyped = blck.body.execution_payload.transactions + template commitments: untyped = blck.body.blob_kzg_commitments + + var i = 0 + for txBytes in transactions: + if txBytes.len == 0 or txBytes[0] != TxEip4844.byte: + continue # Only blob transactions may have blobs + let tx = ? txBytes.readExecutionTransaction() + for vHash in tx.versionedHashes: + if commitments.len <= i: + return err("Extra blobs without matching `blob_kzg_commitments`") + if vHash.data != kzg_commitment_to_versioned_hash(commitments[i]): + return err("Invalid `blob_versioned_hash` at index " & $i) + inc i + if i != commitments.len: + return err("Extra `blob_kzg_commitments` without matching blobs") ok() diff --git a/beacon_chain/spec/mev/fulu_mev.nim b/beacon_chain/spec/mev/fulu_mev.nim index c6dd0cdd16..0ddc9d976a 100644 --- a/beacon_chain/spec/mev/fulu_mev.nim +++ b/beacon_chain/spec/mev/fulu_mev.nim @@ -9,13 +9,12 @@ import ".."/datatypes/[altair, fulu] -from stew/byteutils import to0xHex from ".."/datatypes/phase0 import AttesterSlashing from ../datatypes/bellatrix import ExecutionAddress from ".."/datatypes/capella import SignedBLSToExecutionChange from ".."/datatypes/deneb import BlobsBundle, KzgCommitments from ".."/datatypes/electra import Attestation, AttesterSlashing, - ExecutionRequests + ExecutionRequests, ExecutionPayload from ".."/eth2_merkleization import hash_tree_root type @@ -41,12 +40,12 @@ type deposits*: List[Deposit, Limit MAX_DEPOSITS] voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS] sync_aggregate*: SyncAggregate - execution_payload_header*: ExecutionPayloadHeader + signed_execution_payload_header*: SignedExecutionPayloadHeader bls_to_execution_changes*: List[SignedBLSToExecutionChange, Limit MAX_BLS_TO_EXECUTION_CHANGES] - blob_kzg_commitments*: KzgCommitments # [New in Deneb] - execution_requests*: ExecutionRequests # [New in Electra] + # blob_kzg_commitments*: KzgCommitments # [New in Deneb] + # execution_requests*: ExecutionRequests # [New in Electra] # https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/builder.md#blindedbeaconblock BlindedBeaconBlock* = object @@ -70,7 +69,7 @@ type signature*: ValidatorSig ExecutionPayloadAndBlobsBundle* = object - execution_payload*: ExecutionPayload + execution_payload*: fulu.ExecutionPayload blobs_bundle*: BlobsBundle # Not spec, but suggested by spec @@ -92,11 +91,11 @@ func shortLog*(v: BlindedBeaconBlock): auto = deposits_len: v.body.deposits.len(), voluntary_exits_len: v.body.voluntary_exits.len(), sync_committee_participants: v.body.sync_aggregate.num_active_participants, - block_number: v.body.execution_payload_header.block_number, + block_number: uint64(0), # Default value for block number, # TODO checksum hex? shortlog? - block_hash: to0xHex(v.body.execution_payload_header.block_hash.data), - parent_hash: to0xHex(v.body.execution_payload_header.parent_hash.data), - fee_recipient: to0xHex(v.body.execution_payload_header.fee_recipient.data), + block_hash: "N/A", # Default string for missing block hash + parent_hash: "N/A", # Default string for missing parent hash + fee_recipient: "N/A", # Default string for missing fee recipient bls_to_execution_changes_len: v.body.bls_to_execution_changes.len(), blob_kzg_commitments_len: 0, # Deneb compat ) @@ -125,28 +124,17 @@ func toSignedBlindedBeaconBlock*(blck: fulu.SignedBeaconBlock): deposits: blck.message.body.deposits, voluntary_exits: blck.message.body.voluntary_exits, sync_aggregate: blck.message.body.sync_aggregate, - execution_payload_header: ExecutionPayloadHeader( - parent_hash: blck.message.body.execution_payload.parent_hash, - fee_recipient: blck.message.body.execution_payload.fee_recipient, - state_root: blck.message.body.execution_payload.state_root, - receipts_root: blck.message.body.execution_payload.receipts_root, - logs_bloom: blck.message.body.execution_payload.logs_bloom, - prev_randao: blck.message.body.execution_payload.prev_randao, - block_number: blck.message.body.execution_payload.block_number, - gas_limit: blck.message.body.execution_payload.gas_limit, - gas_used: blck.message.body.execution_payload.gas_used, - timestamp: blck.message.body.execution_payload.timestamp, - extra_data: blck.message.body.execution_payload.extra_data, - base_fee_per_gas: - blck.message.body.execution_payload.base_fee_per_gas, - block_hash: blck.message.body.execution_payload.block_hash, - transactions_root: - hash_tree_root(blck.message.body.execution_payload.transactions), - withdrawals_root: - hash_tree_root(blck.message.body.execution_payload.withdrawals), - blob_gas_used: blck.message.body.execution_payload.blob_gas_used, - excess_blob_gas: blck.message.body.execution_payload.excess_blob_gas), - bls_to_execution_changes: blck.message.body.bls_to_execution_changes, - blob_kzg_commitments: blck.message.body.blob_kzg_commitments, - execution_requests: blck.message.body.execution_requests)), + signed_execution_payload_header: SignedExecutionPayloadHeader( + message: ExecutionPayloadHeader( + parent_block_hash: blck.message.body.signed_execution_payload_header.message.parent_block_hash, + parent_block_root: blck.message.body.signed_execution_payload_header.message.parent_block_root, + gas_limit: blck.message.body.signed_execution_payload_header.message.gas_limit, + builder_index: blck.message.body.signed_execution_payload_header.message.builder_index, + slot: blck.message.body.signed_execution_payload_header.message.slot, + value: blck.message.body.signed_execution_payload_header.message.value, + blob_kzg_commitments_root: blck.message.body.signed_execution_payload_header.message.blob_kzg_commitments_root, + block_hash: blck.message.body.signed_execution_payload_header.message.block_hash + ), + signature: blck.signature), + bls_to_execution_changes: blck.message.body.bls_to_execution_changes)), signature: blck.signature) diff --git a/beacon_chain/spec/network.nim b/beacon_chain/spec/network.nim index e5795ec303..f6564dc4fc 100644 --- a/beacon_chain/spec/network.nim +++ b/beacon_chain/spec/network.nim @@ -109,7 +109,7 @@ func getBlobSidecarTopic*(forkDigest: ForkDigest, func compute_subnet_for_blob_sidecar*(blob_index: BlobIndex): BlobId = BlobId(blob_index mod BLOB_SIDECAR_SUBNET_COUNT) -# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/_features/eip7594/p2p-interface.md#compute_subnet_for_data_column_sidecar +# # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/_features/eip7594/p2p-interface.md#compute_subnet_for_data_column_sidecar func compute_subnet_for_data_column_sidecar*(column_index: ColumnIndex): uint64 = uint64(column_index mod DATA_COLUMN_SIDECAR_SUBNET_COUNT) diff --git a/beacon_chain/spec/signatures.nim b/beacon_chain/spec/signatures.nim index 9197912b14..b2ab7a0997 100644 --- a/beacon_chain/spec/signatures.nim +++ b/beacon_chain/spec/signatures.nim @@ -424,3 +424,93 @@ proc verify_bls_to_execution_change_signature*( let signing_root = compute_bls_to_execution_change_signing_root( genesisFork, genesis_validators_root, msg.message) blsVerify(pubkey, signing_root.data, signature) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#new-verify_execution_payload_header_signature +func compute_execution_payload_header_signing_root*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: fulu.SignedExecutionPayloadHeader, + state: fulu.BeaconState): Eth2Digest = + + let + epoch = get_current_epoch(state) + domain = get_domain( + fork, DOMAIN_BEACON_BUILDER, epoch, genesis_validators_root) + compute_signing_root(msg.message, domain) + +func get_execution_payload_header_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: SignedExecutionPayloadHeader, state: fulu.BeaconState, + privkey: ValidatorPrivKey): CookedSig = + let signing_root = compute_execution_payload_header_signing_root( + fork, genesis_validators_root, msg, state) + blsSign(privkey, signing_root.data) + +proc verify_execution_payload_header_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: fulu.SignedExecutionPayloadHeader, state: fulu.BeaconState, + pubkey: ValidatorPubKey | CookedPubKey, + signature: SomeSig): bool = + let signing_root = compute_execution_payload_header_signing_root( + fork, genesis_validators_root, msg, state) + blsVerify(pubkey, signing_root.data, signature) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#new-verify_execution_payload_envelope_signature +func compute_execution_payload_envelope_signing_root*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: fulu.SignedExecutionPayloadEnvelope, + state: fulu.BeaconState): Eth2Digest = + + let + epoch = get_current_epoch(state) + domain = get_domain( + fork, DOMAIN_BEACON_BUILDER, epoch, genesis_validators_root) + compute_signing_root(msg.message, domain) + +func get_execution_payload_envelope_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: SignedExecutionPayloadEnvelope, state: fulu.BeaconState, + privkey: ValidatorPrivKey): CookedSig = + let signing_root = compute_execution_payload_envelope_signing_root( + fork, genesis_validators_root, msg, state) + blsSign(privkey, signing_root.data) + +proc verify_execution_payload_envelope_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + msg: fulu.SignedExecutionPayloadEnvelope, + state: fulu.BeaconState, pubkey: ValidatorPubKey | CookedPubKey, + signature: SomeSig): bool = + let signing_root = compute_execution_payload_envelope_signing_root( + fork, genesis_validators_root, msg, state) + blsVerify(pubkey, signing_root.data, signature) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/validator.md#payload-timeliness-attestation +func compute_payload_attestation_message_signing_root*( + fork: Fork, genesis_validators_root: Eth2Digest, + attestation: PayloadAttestationMessage, + state: fulu.BeaconState): Eth2Digest = + + let + epoch = get_current_epoch(state) + domain = get_domain( + fork, DOMAIN_BEACON_BUILDER, epoch, genesis_validators_root) + compute_signing_root(attestation.data, domain) + +func get_payload_attestation_message_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + attestation: PayloadAttestationMessage, + state: fulu.BeaconState, + privkey: ValidatorPrivKey): CookedSig = + let signing_root = compute_payload_attestation_message_signing_root( + fork, genesis_validators_root, attestation, state) + blsSign(privkey, signing_root.data) + +proc verify_payload_attestation_message_signature*( + fork: Fork, genesis_validators_root: Eth2Digest, + attestation: PayloadAttestationMessage, + state: fulu.BeaconState, + pubkey: ValidatorPubKey | CookedPubKey, + signature: SomeSig, + ): bool = + let signing_root = compute_payload_attestation_message_signing_root( + fork, genesis_validators_root, attestation, state) + blsVerify(pubkey, signing_root.data, signature) \ No newline at end of file diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index 876379bbd4..c0d30d0694 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -431,27 +431,50 @@ func partialBeaconBlock*( execution_payload: ForkyExecutionPayloadForSigning, execution_requests: ExecutionRequests): auto = const consensusFork = typeof(state).kind - + # https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/validator.md#preparing-for-a-beaconblock - consensusFork.BeaconBlock( - slot: state.data.slot, - proposer_index: proposer_index.uint64, - parent_root: state.latest_block_root, - body: consensusFork.BeaconBlockBody( - randao_reveal: randao_reveal, - eth1_data: eth1_data, - graffiti: graffiti, - proposer_slashings: validator_changes.proposer_slashings, - attester_slashings: validator_changes.electra_attester_slashings, - attestations: - List[electra.Attestation, Limit MAX_ATTESTATIONS_ELECTRA](attestations), - deposits: List[Deposit, Limit MAX_DEPOSITS](deposits), - voluntary_exits: validator_changes.voluntary_exits, - sync_aggregate: sync_aggregate, - execution_payload: execution_payload.executionPayload, - bls_to_execution_changes: validator_changes.bls_to_execution_changes, - blob_kzg_commitments: execution_payload.blobsBundle.commitments, - execution_requests: execution_requests)) + when consensusFork == ConsensusFork.Fulu: + fulu.BeaconBlock( + slot: state.data.slot, + proposer_index: proposer_index.uint64, + parent_root: state.latest_block_root, + state_root: Eth2Digest(), + body: fulu.BeaconBlockBody( + randao_reveal: randao_reveal, + eth1_data: eth1_data, + graffiti: graffiti, + proposer_slashings: validator_changes.proposer_slashings, + attester_slashings: validator_changes.electra_attester_slashings, + attestations: + List[electra.Attestation, Limit MAX_ATTESTATIONS_ELECTRA](attestations), + deposits: List[Deposit, Limit MAX_DEPOSITS](deposits), + voluntary_exits: validator_changes.voluntary_exits, + sync_aggregate: sync_aggregate, + bls_to_execution_changes: validator_changes.bls_to_execution_changes + ) + ) + else: + consensusFork.BeaconBlock( + slot: state.data.slot, + proposer_index: proposer_index.uint64, + parent_root: state.latest_block_root, + body: consensusFork.BeaconBlockBody( + randao_reveal: randao_reveal, + eth1_data: eth1_data, + graffiti: graffiti, + proposer_slashings: validator_changes.proposer_slashings, + attester_slashings: validator_changes.electra_attester_slashings, + attestations: + List[electra.Attestation, Limit MAX_ATTESTATIONS_ELECTRA](attestations), + deposits: List[Deposit, Limit MAX_DEPOSITS](deposits), + voluntary_exits: validator_changes.voluntary_exits, + sync_aggregate: sync_aggregate, + execution_payload: execution_payload.executionPayload, + bls_to_execution_changes: validator_changes.bls_to_execution_changes, + blob_kzg_commitments: execution_payload.blobsBundle.commitments, + execution_requests: execution_requests + ) + ) proc makeBeaconBlockWithRewards*( cfg: RuntimeConfig, @@ -557,8 +580,9 @@ proc makeBeaconBlockWithRewards*( else: raiseAssert "Attempt to use non-Electra payload with post-Deneb state" elif consensusFork == ConsensusFork.Fulu: - forkyState.data.latest_execution_payload_header.transactions_root = - transactions_root.get + # transaction root removed for epbs blocks + # forkyState.data.latest_execution_payload_header.transactions_root = + # transactions_root.get debugFuluComment "verify (again) that this is what builder API needs" when executionPayload is fulu.ExecutionPayloadForSigning: diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index b8e42ceb0b..3e91f3f92a 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -14,7 +14,8 @@ # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/capella/beacon-chain.md#block-processing # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/deneb/beacon-chain.md#block-processing # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.7/specs/electra/beacon-chain.md#block-processing -# +# https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#block-processing + # The entry point is `process_block` which is at the bottom of this file. # # General notes about the code: @@ -29,7 +30,8 @@ import ../extras, ./datatypes/[phase0, altair, bellatrix, deneb], "."/[beaconstate, eth2_merkleization, helpers, validator, signatures], - kzg4844/kzg_abi, kzg4844/kzg + kzg4844/kzg_abi, kzg4844/kzg, + eip7732_helpers from std/algorithm import fill, sorted from std/sequtils import count, filterIt, mapIt @@ -1060,6 +1062,7 @@ type SomeFuluBeaconBlockBody = fulu.TrustedBeaconBlockBody # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/beacon-chain.md#modified-process_execution_payload + proc process_execution_payload*( state: var fulu.BeaconState, body: SomeFuluBeaconBlockBody, notify_new_payload: fulu.ExecutePayload): Result[void, cstring] = @@ -1164,6 +1167,216 @@ func process_withdrawals*( ok() +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#process_payload_attestation +proc process_payload_attestation*(state: var fulu.BeaconState, + payload_attestation: PayloadAttestation, + cache: var StateCache): Result[void, cstring] = + # Check that the attestation is for the parent beacon block + let data = payload_attestation.data + if not (data.beacon_block_root == state.latest_block_header.parent_root): + return err("process_payload_attestation: beacon block and latest block mismatch") + + # Check that the attestation is for the previous slot + if data.slot + 1 != state.slot: + return err("process_payload_attestation: slot mismatch") + + # Verify signature + let indexed_payload_attestation = + get_indexed_payload_attestation( + state, data.slot, payload_attestation, cache + ) + if not is_valid_indexed_payload_attestation( + state, indexed_payload_attestation): + return err("process_payload_attestation: signature verification failed") + + let epoch_participation = + if state.slot mod SLOTS_PER_EPOCH == 0: + unsafeAddr state.previous_epoch_participation + else: + unsafeAddr state.current_epoch_participation + + let + payload_present = data.slot == state.latest_full_slot + voted_present = data.payload_status == uint8(PAYLOAD_PRESENT) + + const proposer_reward_denominator = + (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR div + PROPOSER_WEIGHT + + let + proposer_index = get_beacon_proposer_index(state, cache).valueOr: + # We're processing a block, so this can't happen, in theory (!) + return err("process_sync_aggregate: no proposer") + total_active_balance = get_total_active_balance(state, cache) + + # Return early if the attestation is for the wrong payload status + if voted_present != payload_present: + # Unset the flags in case they were set by an equivocating PTC attestation + var proposer_penalty_numerator: Gwei = Gwei(0) + for index in indexed_payload_attestation.attesting_indices: + for flag_index, weight in PARTICIPATION_FLAG_WEIGHTS: + if has_flag(epoch_participation[].item(index), flag_index): + epoch_participation[].item(index) = + remove_flag(epoch_participation[].item(index), flag_index) + proposer_penalty_numerator += + get_base_reward_per_increment(total_active_balance) * weight + + let proposer_penalty = + Gwei(2 * proposer_penalty_numerator div Gwei(proposer_reward_denominator)) + decrease_balance(state, proposer_index, proposer_penalty) + + # Reward the proposer and set all the participation flags in case of correct attestations + var proposer_reward_numerator: Gwei = Gwei(0) + for index in indexed_payload_attestation.attesting_indices: + for flag_index, weight in PARTICIPATION_FLAG_WEIGHTS: + if not has_flag(epoch_participation[].item(index), flag_index): + epoch_participation[].item(index) = + add_flag(epoch_participation[].item(index), flag_index) + proposer_reward_numerator += + get_base_reward_per_increment(total_active_balance) * weight + + let proposer_reward = + Gwei(proposer_reward_numerator div Gwei(proposer_reward_denominator)) + increase_balance(state, proposer_index, proposer_reward) + + ok() + +proc process_operations_eip7732( + cfg: RuntimeConfig, state: var ForkyBeaconState, + body: SomeForkyBeaconBlockBody, base_reward_per_increment: Gwei, + flags: UpdateFlags, cache: var StateCache): Result[BlockRewards, cstring] = + # Verify that outstanding deposits are processed up to the maximum number of + # deposits + when typeof(body).kind >= ConsensusFork.Electra: + # Disable former deposit mechanism once all prior deposits are processed + let + eth1_deposit_index_limit = + min(state.eth1_data.deposit_count, state.deposit_requests_start_index) + req_deposits = + # Otherwise wraps because unsigned; Python spec semantics would result in + # negative difference, which would be impossible for len(...) to match. + if state.eth1_deposit_index < eth1_deposit_index_limit: + if eth1_deposit_index_limit < state.eth1_deposit_index: + return err("eth1_deposit_index_limit < state.eth1_deposit_index") + min( + MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index) + else: + 0 + else: + # Otherwise wraps because unsigned; Python spec semantics would result in + # negative difference, which would be impossible for len(...) to match. + if state.eth1_data.deposit_count < state.eth1_deposit_index: + return err("state.eth1_data.deposit_count < state.eth1_deposit_index") + let req_deposits = min( + MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + if body.deposits.lenu64 != req_deposits: + return err("incorrect number of deposits") + + var operations_rewards: BlockRewards + + # It costs a full validator set scan to construct these values; only do so if + # there will be some kind of exit. + # TODO Electra doesn't use exit_queue_info, don't calculate + var + exit_queue_info = + if body.proposer_slashings.len + body.attester_slashings.len + + body.voluntary_exits.len > 0: + get_state_exit_queue_info(state) + else: + default(ExitQueueInfo) # not used + bsv_use = + body.deposits.len > 0 + bsv = + if bsv_use: + sortValidatorBuckets(state.validators.asSeq) + else: + nil # this is a logic error, effectively assert + + for op in body.proposer_slashings: + let (proposer_slashing_reward, new_exit_queue_info) = + ? process_proposer_slashing(cfg, state, op, flags, exit_queue_info, cache) + operations_rewards.proposer_slashings += proposer_slashing_reward + exit_queue_info = new_exit_queue_info + for op in body.attester_slashings: + let (attester_slashing_reward, new_exit_queue_info) = + ? process_attester_slashing(cfg, state, op, flags, exit_queue_info, cache) + operations_rewards.attester_slashings += attester_slashing_reward + exit_queue_info = new_exit_queue_info + for op in body.attestations: + operations_rewards.attestations += + ? process_attestation(state, op, flags, base_reward_per_increment, cache) + for op in body.deposits: + ? process_deposit(cfg, state, bsv[], op, flags) + for op in body.voluntary_exits: + exit_queue_info = ? process_voluntary_exit( + cfg, state, op, flags, exit_queue_info, cache) + when typeof(body).kind >= ConsensusFork.Capella: + for op in body.bls_to_execution_changes: + ? process_bls_to_execution_change(cfg, state, op) + + when typeof(body).kind >= ConsensusFork.Fulu: + for op in body.payload_attestations: + ? process_payload_attestation(state, op, cache) + + ok(operations_rewards) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#withdrawals +func process_withdrawals_eip7732*(state: var fulu.BeaconState): + Result[void, cstring] = + + # Return early if the parent block was empty + if not is_parent_block_full(state): + return err("parent block is empty") + + let (withdrawals, partial_withdrawals_count) = + get_expected_withdrawals_with_partial_count(state) + + # Update pending partial withdrawals [New in Electra:EIP7251] + # Moved slightly earlier to be in same when block + state.pending_partial_withdrawals = + HashList[PendingPartialWithdrawal, Limit PENDING_PARTIAL_WITHDRAWALS_LIMIT].init( + state.pending_partial_withdrawals.asSeq[partial_withdrawals_count .. ^1]) + + var withdrawals_list: List[Withdrawal, Limit MAX_WITHDRAWALS_PER_PAYLOAD] + + # Loop through the withdrawals and add them to withdrawals_list + for i in 0 ..< min(len(withdrawals), MAX_WITHDRAWALS_PER_PAYLOAD): + withdrawals_list[i] = withdrawals[i] + + state.latest_withdrawals_root = hash_tree_root(withdrawals_list) + + for i in 0 ..< len(withdrawals): + let validator_index = + ValidatorIndex.init(withdrawals[i].validator_index).valueOr: + return err("process_withdrawals: invalid validator index") + decrease_balance( + state, validator_index, withdrawals[i].amount) + + # Update the next withdrawal index if this block contained withdrawals + if len(withdrawals) != 0: + let latest_withdrawal = withdrawals[^1] + state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) + + # Update the next validator index to start the next withdrawal sweep + if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + # Next sweep starts after the latest withdrawal's validator index + let next_validator_index = + (withdrawals[^1].validator_index + 1) mod + lenu64(state.validators) + state.next_withdrawal_validator_index = next_validator_index + else: + # Advance sweep by the max length of the sweep if there was not a full set + # of withdrawals + let + next_index = + state.next_withdrawal_validator_index + + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + next_validator_index = next_index mod lenu64(state.validators) + state.next_withdrawal_validator_index = next_validator_index + + ok() + # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/deneb/beacon-chain.md#kzg_commitment_to_versioned_hash func kzg_commitment_to_versioned_hash*( kzg_commitment: KzgCommitment): VersionedHash = @@ -1373,6 +1586,49 @@ proc process_block*( type SomeFuluBlock = fulu.BeaconBlock | fulu.SigVerifiedBeaconBlock | fulu.TrustedBeaconBlock +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#new-process_execution_payload_header +proc process_execution_payload_header*(state: var fulu.BeaconState, + blck: SomeFuluBlock): Result[void, cstring] = + + let signed_header = blck.body.signed_execution_payload_header + + for vidx in state.validators.vindices: + let pubkey = state.validators.item(vidx).pubkey() + + if not verify_execution_payload_header_signature( + state.fork, state.genesis_validators_root, signed_header, + state, pubkey, signed_header.signature): + return err("payload_header: signature verification failure") + + let + header = signed_header.message + + builder_index = ValidatorIndex.init(header.builder_index).valueOr: + return err("process_execution_payload_header: builder index out of range") + amount = header.value + + if state.balances.item(builder_index) < amount: + return err("insufficient balance") + + if header.slot != blck.slot: + return err("slot mismatch") + + if header.parent_block_hash != state.latest_block_hash: + return err("parent block hash mismatch") + + if header.parent_block_root != blck.parent_root: + return err("parent block root mismatch") + + let proposer_index = ValidatorIndex.init(blck.proposer_index).valueOr: + return err("process_execution_payload_header: proposer index out of range") + + decrease_balance(state, builder_index, amount) + increase_balance(state, proposer_index, amount) + + state.latest_execution_payload_header = header + + ok() + proc process_block*( cfg: RuntimeConfig, state: var fulu.BeaconState, blck: SomeFuluBlock, @@ -1386,10 +1642,11 @@ proc process_block*( # Consensus specs v1.4.0 unconditionally assume is_execution_enabled is # true, but intentionally keep such a check. if is_execution_enabled(state, blck.body): - ? process_withdrawals(state, blck.body.execution_payload) - ? process_execution_payload( - state, blck.body, - func(_: fulu.ExecutionPayload): bool = true) + ? process_withdrawals_eip7732(state) # [Modified in EIP-7732] + # ? process_execution_payload( + # state, blck.body, + # func(_: fulu.ExecutionPayload): bool = true) # [Removed in EIP-7732] + ? process_execution_payload_header(state, blck) # [New in EIP-7732] ? process_randao(state, blck.body, flags, cache) ? process_eth1_data(state, blck.body) @@ -1397,8 +1654,8 @@ proc process_block*( total_active_balance = get_total_active_balance(state, cache) base_reward_per_increment = get_base_reward_per_increment(total_active_balance) - var operations_rewards = ? process_operations( - cfg, state, blck.body, base_reward_per_increment, flags, cache) + var operations_rewards = ? process_operations_eip7732( + cfg, state, blck.body, base_reward_per_increment, flags, cache) # [Modified in EIP-7732] operations_rewards.sync_aggregate = ? process_sync_aggregate( state, blck.body.sync_aggregate, total_active_balance, flags, cache) diff --git a/beacon_chain/sync/request_manager.nim b/beacon_chain/sync/request_manager.nim index 79767e553c..f35d7e030b 100644 --- a/beacon_chain/sync/request_manager.nim +++ b/beacon_chain/sync/request_manager.nim @@ -304,7 +304,10 @@ proc getMissingBlobs(rman: RequestManager): seq[BlobIdentifier] = ready: seq[Eth2Digest] for blobless in rman.quarantine[].peekBlobless(): withBlck(blobless): - when consensusFork >= ConsensusFork.Deneb: + when consensusFork == ConsensusFork.Fulu: + # For Fulu, return an empty sequence + return fetches + elif consensusFork >= ConsensusFork.Deneb: # give blobs a chance to arrive over gossip if forkyBlck.message.slot == wallSlot and delay < waitDur: debug "Not handling missing blobs early in slot" diff --git a/beacon_chain/sync/sync_manager.nim b/beacon_chain/sync/sync_manager.nim index 2e5decf00d..daffd5ab66 100644 --- a/beacon_chain/sync/sync_manager.nim +++ b/beacon_chain/sync/sync_manager.nim @@ -246,7 +246,10 @@ func groupBlobs*( blob_cursor = 0 for block_idx, blck in blocks: withBlck(blck[]): - when consensusFork >= ConsensusFork.Deneb: + when consensusFork == ConsensusFork.Fulu: + # Skip blob processing for fulu beacon blocks as they do not contain blobs + continue + elif consensusFork >= ConsensusFork.Deneb: template kzgs: untyped = forkyBlck.message.body.blob_kzg_commitments if kzgs.len == 0: continue diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index 770cc4ab7d..f27fc4e1ae 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -978,7 +978,10 @@ proc getBlindedBlockParts[ template actualEPH: untyped = executionPayloadHeader.get.blindedBlckPart.execution_payload_header let - withdrawals_root = Opt.some actualEPH.withdrawals_root + withdrawals_root = when compiles(actualEPH.withdrawals_root): + Opt.some(actualEPH.withdrawals_root) + else: + Opt.none(Eth2Digest) kzg_commitments = Opt.some( executionPayloadHeader.get.blindedBlckPart.blob_kzg_commitments) diff --git a/tests/test_sync_manager.nim b/tests/test_sync_manager.nim index 3071aa92d5..256e9c2af4 100644 --- a/tests/test_sync_manager.nim +++ b/tests/test_sync_manager.nim @@ -77,7 +77,9 @@ suite "SyncManager test suite": var res = newSeq[ref BlobSidecar](len(slots)) for blck in blocks: withBlck(blck[]): - when consensusFork >= ConsensusFork.Deneb: + when consensusFork == ConsensusFork.Fulu: + continue + elif consensusFork >= ConsensusFork.Deneb: template kzgs: untyped = forkyBlck.message.body.blob_kzg_commitments for i, slot in slots: if slot == forkyBlck.message.slot: diff --git a/tests/test_toblindedblock.nim b/tests/test_toblindedblock.nim index 8e6b9e9156..ed7768ea2f 100644 --- a/tests/test_toblindedblock.nim +++ b/tests/test_toblindedblock.nim @@ -68,79 +68,85 @@ template bellatrix_steps() = do_check b.message.body.sync_aggregate.sync_committee_signature = nondefaultValidatorSig - do_check - b.message.body.execution_payload.parent_hash = Eth2Digest.fromHex( - "0x941bdf6ccf731a7ede6bac0c9533ecee5e3dc5081ea59d57c3fd8c624eeca85d") - do_check - b.message.body.execution_payload.fee_recipient = - ExecutionAddress.fromHex("0x1234567812345678123456781234567812345678") - do_check - b.message.body.execution_payload.state_root = Eth2Digest.fromHex( - "0x9e7d9bca96a9d0af9013ad6abb8708988beef02d58c16ba1a90075960b99c2ff") - do_check - b.message.body.execution_payload.receipts_root = Eth2Digest.fromHex( - "0x0e66a5007cf7bb16f4398adbbd01b34067a80faaef41a0a6be324c5fdb93a6df") - do_check - b.message.body.execution_payload.logs_bloom.data[0] = 2 - do_check - b.message.body.execution_payload.prev_randao = Eth2Digest.fromHex( - "0x8aa830156370e6a5ec7679d7e5ee712dd87f24fef76a1954a03c1df8c68bc0fd") - do_check - b.message.body.execution_payload.block_number = 3 - do_check - b.message.body.execution_payload.gas_limit = 4 - do_check - b.message.body.execution_payload.gas_used = 5 - do_check - b.message.body.execution_payload.timestamp = 6 - do_check - check: b.message.body.execution_payload.extra_data.add 0'u8 - do_check - b.message.body.execution_payload.base_fee_per_gas = 7.u256 - do_check - b.message.body.execution_payload.block_hash = Eth2Digest.fromHex( - "0x4b1aed517ac48bfbf6ab19846923d5256897fbc934c20ca5b8c486bfe71c6ef1") - do_check - check: b.message.body.execution_payload.transactions.add default(Transaction) - do_check + when consensusFork < ConsensusFork.Fulu: + debugFuluComment "Execution payload removed from fulu" + do_check + b.message.body.execution_payload.parent_hash = Eth2Digest.fromHex( + "0x941bdf6ccf731a7ede6bac0c9533ecee5e3dc5081ea59d57c3fd8c624eeca85d") + do_check + b.message.body.execution_payload.fee_recipient = + ExecutionAddress.fromHex("0x1234567812345678123456781234567812345678") + do_check + b.message.body.execution_payload.state_root = Eth2Digest.fromHex( + "0x9e7d9bca96a9d0af9013ad6abb8708988beef02d58c16ba1a90075960b99c2ff") + do_check + b.message.body.execution_payload.receipts_root = Eth2Digest.fromHex( + "0x0e66a5007cf7bb16f4398adbbd01b34067a80faaef41a0a6be324c5fdb93a6df") + do_check + b.message.body.execution_payload.logs_bloom.data[0] = 2 + do_check + b.message.body.execution_payload.prev_randao = Eth2Digest.fromHex( + "0x8aa830156370e6a5ec7679d7e5ee712dd87f24fef76a1954a03c1df8c68bc0fd") + do_check + b.message.body.execution_payload.block_number = 3 + do_check + b.message.body.execution_payload.gas_limit = 4 + do_check + b.message.body.execution_payload.gas_used = 5 + do_check + b.message.body.execution_payload.timestamp = 6 + do_check + check: b.message.body.execution_payload.extra_data.add 0'u8 + do_check + b.message.body.execution_payload.base_fee_per_gas = 7.u256 + do_check + b.message.body.execution_payload.block_hash = Eth2Digest.fromHex( + "0x4b1aed517ac48bfbf6ab19846923d5256897fbc934c20ca5b8c486bfe71c6ef1") + do_check + check: b.message.body.execution_payload.transactions.add default(Transaction) + do_check template capella_steps() = check: b.message.body.bls_to_execution_changes.add( default(SignedBLSToExecutionChange)) - do_check - check: b.message.body.execution_payload.withdrawals.add(default( - Withdrawal)) - do_check + when consensusFork < ConsensusFork.Fulu: + do_check + check: b.message.body.execution_payload.withdrawals.add(default( + Withdrawal)) + do_check template deneb_steps() = - b.message.body.execution_payload.blob_gas_used = 8 - do_check - b.message.body.execution_payload.excess_blob_gas = 9 - do_check - check: b.message.body.blob_kzg_commitments.add(default(KzgCommitment)) - do_check + when consensusFork < ConsensusFork.Fulu: + b.message.body.execution_payload.blob_gas_used = 8 + do_check + b.message.body.execution_payload.excess_blob_gas = 9 + do_check + check: b.message.body.blob_kzg_commitments.add(default(KzgCommitment)) + do_check template electra_steps() = - check: b.message.body.execution_requests.deposits.add( - default(DepositRequest)) - do_check - check: b.message.body.execution_requests.withdrawals.add( - default(WithdrawalRequest)) - do_check - check: b.message.body.execution_requests.consolidations.add( - default(ConsolidationRequest)) - do_check + when consensusFork < ConsensusFork.Fulu: + check: b.message.body.execution_requests.deposits.add( + default(DepositRequest)) + do_check + check: b.message.body.execution_requests.withdrawals.add( + default(WithdrawalRequest)) + do_check + check: b.message.body.execution_requests.consolidations.add( + default(ConsolidationRequest)) + do_check template fulu_steps() = - check: b.message.body.execution_requests.deposits.add( - default(DepositRequest)) - do_check - check: b.message.body.execution_requests.withdrawals.add( - default(WithdrawalRequest)) - do_check - check: b.message.body.execution_requests.consolidations.add( - default(ConsolidationRequest)) - do_check + when consensusFork < ConsensusFork.Fulu: + check: b.message.body.execution_requests.deposits.add( + default(DepositRequest)) + do_check + check: b.message.body.execution_requests.withdrawals.add( + default(WithdrawalRequest)) + do_check + check: b.message.body.execution_requests.consolidations.add( + default(ConsolidationRequest)) + do_check suite "Blinded block conversions": withAll(ConsensusFork): From b62119b51a9f7b6cc9e142886e41b1bf666d0476 Mon Sep 17 00:00:00 2001 From: Caleb Omoniyi <81532297+Tomi-3-0@users.noreply.github.com> Date: Sun, 29 Dec 2024 22:15:34 +0000 Subject: [PATCH 2/4] Refactor: Adjusted epbs fork-specific logic operations across blob quarantine, gossip validation, sync, and validator flows --- .../blob_quarantine.nim | 2 +- .../gossip_processing/gossip_validation.nim | 13 ++-- beacon_chain/spec/helpers.nim | 49 +++++++++------ beacon_chain/sync/sync_manager.nim | 21 ++++--- beacon_chain/validators/beacon_validators.nim | 59 +++++++++++++------ beacon_chain/validators/message_router.nim | 4 +- .../validators/message_router_mev.nim | 31 +++++++--- 7 files changed, 119 insertions(+), 60 deletions(-) diff --git a/beacon_chain/consensus_object_pools/blob_quarantine.nim b/beacon_chain/consensus_object_pools/blob_quarantine.nim index f99aa6f169..7deaf9d8d6 100644 --- a/beacon_chain/consensus_object_pools/blob_quarantine.nim +++ b/beacon_chain/consensus_object_pools/blob_quarantine.nim @@ -76,7 +76,7 @@ func popBlobs*( fulu.SignedBeaconBlock): seq[ref BlobSidecar] = var r: seq[ref BlobSidecar] = @[] - # Handle `fulu` blocks: skip processing blobs + # skip processing blobs for fulu when typeof(blck).kind == ConsensusFork.Fulu: return r else: diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index d42d1c2be2..90cb46eac2 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -341,11 +341,14 @@ template validateBeaconBlockBellatrix( withState(dag.headState): compute_timestamp_at_slot( forkyState.data, signed_beacon_block.message.slot) - if not (signed_beacon_block.message.body.execution_payload.timestamp == - timestampAtSlot): - quarantine[].addUnviable(signed_beacon_block.root) - return dag.checkedReject( - "BeaconBlock: mismatched execution payload timestamp") + when signed_beacon_block is fulu.SignedBeaconBlock: + discard + else: + if not (signed_beacon_block.message.body.execution_payload.timestamp == + timestampAtSlot): + quarantine[].addUnviable(signed_beacon_block.root) + return dag.checkedReject( + "BeaconBlock: mismatched execution payload timestamp") # The condition: # [REJECT] The block's parent (defined by `block.parent_root`) passes all diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index b524b4800c..7ec080ffe8 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -231,25 +231,36 @@ func create_blob_sidecars*( fulu.SignedBeaconBlock, kzg_proofs: KzgProofs, blobs: Blobs): seq[BlobSidecar] = - template kzg_commitments: untyped = - forkyBlck.message.body.blob_kzg_commitments - doAssert kzg_proofs.len == blobs.len - doAssert kzg_proofs.len == kzg_commitments.len - - var res = newSeqOfCap[BlobSidecar](blobs.len) - let signedBlockHeader = forkyBlck.toSignedBeaconBlockHeader() - for i in 0 ..< blobs.lenu64: - var sidecar = BlobSidecar( - index: i, - blob: blobs[i], - kzg_commitment: kzg_commitments[i], - kzg_proof: kzg_proofs[i], - signed_block_header: signedBlockHeader) - forkyBlck.message.body.build_proof( - kzg_commitment_inclusion_proof_gindex(i), - sidecar.kzg_commitment_inclusion_proof).expect("Valid gindex") - res.add(sidecar) - res + + when forkyBlck is fulu.SignedBeaconBlock: + return @[] + else: + template kzg_commitments: untyped = + forkyBlck.message.body.blob_kzg_commitments + + if kzg_proofs.len == 0 or blobs.len == 0 or kzg_commitments.len == 0: + return @[] + + doAssert kzg_proofs.len == blobs.len + doAssert kzg_proofs.len == kzg_commitments.len + + var res = newSeqOfCap[BlobSidecar](blobs.len) + let signedBlockHeader = forkyBlck.toSignedBeaconBlockHeader() + + if blobs.len > 0: + for i in 0 ..< blobs.lenu64: + var sidecar = BlobSidecar( + index: i, + blob: blobs[i], + kzg_commitment: kzg_commitments[i], + kzg_proof: kzg_proofs[i], + signed_block_header: signedBlockHeader) + forkyBlck.message.body.build_proof( + kzg_commitment_inclusion_proof_gindex(i), + sidecar.kzg_commitment_inclusion_proof).expect("Valid gindex") + res.add(sidecar) + + res # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/light-client/sync-protocol.md#is_sync_committee_update template is_sync_committee_update*(update: SomeForkyLightClientUpdate): bool = diff --git a/beacon_chain/sync/sync_manager.nim b/beacon_chain/sync/sync_manager.nim index daffd5ab66..77be5bd511 100644 --- a/beacon_chain/sync/sync_manager.nim +++ b/beacon_chain/sync/sync_manager.nim @@ -324,11 +324,15 @@ proc getSyncBlockData*[T]( let (shouldGetBlob, blobsCount) = withBlck(blocksRange[0][]): when consensusFork >= ConsensusFork.Deneb: - let res = len(forkyBlck.message.body.blob_kzg_commitments) - if res > 0: - (true, res) - else: + when consensusFork == ConsensusFork.Fulu: + # Fulu fork: No blob-related fields (false, 0) + else: + let res = len(forkyBlck.message.body.blob_kzg_commitments) + if res > 0: + (true, res) + else: + (false, 0) else: (false, 0) @@ -528,10 +532,11 @@ proc syncStep[A, B]( var hasBlobs = false for blck in blockData: withBlck(blck[]): - when consensusFork >= ConsensusFork.Deneb: - if forkyBlck.message.body.blob_kzg_commitments.len > 0: - hasBlobs = true - break + when consensusFork >= ConsensusFork.Deneb and + consensusFork != ConsensusFork.Fulu: + if forkyBlck.message.body.blob_kzg_commitments.len > 0: + hasBlobs = true + break hasBlobs let blobData = diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index f27fc4e1ae..953c1cb695 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -731,12 +731,17 @@ func constructSignableBlindedBlock[T: electra_mev.SignedBlindedBeaconBlock | # https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/validator.md#block-proposal copyFields(blindedBlock.message, blck, blckFields) copyFields(blindedBlock.message.body, blck.body, blckBodyFields) - assign( - blindedBlock.message.body.execution_payload_header, - blindedBundle.execution_payload_header) - assign( - blindedBlock.message.body.blob_kzg_commitments, - blindedBundle.blob_kzg_commitments) + when T is fulu_mev.SignedBlindedBeaconBlock: + assign( + blindedBlock.message.body.signed_execution_payload_header, + blindedBundle.execution_payload_header) + else: + assign( + blindedBlock.message.body.execution_payload_header, + blindedBundle.execution_payload_header) + assign( + blindedBlock.message.body.blob_kzg_commitments, + blindedBundle.blob_kzg_commitments) blindedBlock @@ -753,12 +758,19 @@ func constructSignableBlindedBlock[T: fulu_mev.SignedBlindedBeaconBlock]( # https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/validator.md#block-proposal copyFields(blindedBlock.message, blck, blckFields) copyFields(blindedBlock.message.body, blck.body, blckBodyFields) - assign( - blindedBlock.message.body.execution_payload_header, - blindedBundle.execution_payload_header) - assign( - blindedBlock.message.body.blob_kzg_commitments, - blindedBundle.blob_kzg_commitments) + + when T is fulu_mev.SignedBlindedBeaconBlock: + assign( + blindedBlock.message.body.signed_execution_payload_header.message, + blindedBundle.execution_payload_header) + else: + assign( + blindedBlock.message.body.execution_payload_header, + blindedBundle.execution_payload_header) + assign( + blindedBlock.message.body.blob_kzg_commitments, + blindedBundle.blob_kzg_commitments) + blindedBlock @@ -826,11 +838,11 @@ func constructPlainBlindedBlock[T: fulu_mev.BlindedBeaconBlock]( copyFields(blindedBlock, blck, blckFields) copyFields(blindedBlock.body, blck.body, blckBodyFields) assign( - blindedBlock.body.execution_payload_header, + blindedBlock.body.signed_execution_payload_header.message, blindedBundle.execution_payload_header) - assign( - blindedBlock.body.blob_kzg_commitments, - blindedBundle.blob_kzg_commitments) + # assign( + # blindedBlock.body.blob_kzg_commitments, + # blindedBundle.blob_kzg_commitments) blindedBlock @@ -997,7 +1009,10 @@ proc getBlindedBlockParts[ let newBlock = await makeBeaconBlockForHeadAndSlot( PayloadType, node, randao, validator_index, graffiti, head, slot, execution_payload = Opt.some shimExecutionPayload, - transactions_root = Opt.some actualEPH.transactions_root, + transactions_root = when compiles(actualEPH.withdrawals_root): + Opt.some(actualEPH.withdrawals_root) + else: + Opt.none(Eth2Digest), execution_payload_root = Opt.some hash_tree_root(actualEPH), withdrawals_root = withdrawals_root, kzg_commitments = kzg_commitments, @@ -2215,7 +2230,7 @@ proc makeMaybeBlindedBeaconBlockForHeadAndSlotImpl[ResultType]( doAssert engineBid.blck.kind == consensusFork template forkyBlck: untyped = engineBid.blck.forky(consensusFork) - when consensusFork >= ConsensusFork.Deneb: + when consensusFork >= ConsensusFork.Deneb and consensusFork != ConsensusFork.Fulu: let blobsBundle = engineBid.blobsBundleOpt.get() doAssert blobsBundle.commitments == forkyBlck.body.blob_kzg_commitments ResultType.ok(( @@ -2227,6 +2242,14 @@ proc makeMaybeBlindedBeaconBlockForHeadAndSlotImpl[ResultType]( blobs: blobsBundle.blobs)), executionValue: Opt.some(engineBid.executionPayloadValue), consensusValue: Opt.some(engineBid.consensusBlockValue))) + elif consensusFork == ConsensusFork.Fulu: + # Fulu fork: no blobs or KZG commitments + ResultType.ok(( + blck: consensusFork.MaybeBlindedBeaconBlock( + isBlinded: false, + data: consensusFork.BlockContents(`block`: forkyBlck)), + executionValue: Opt.some(engineBid.executionPayloadValue), + consensusValue: Opt.some(engineBid.consensusBlockValue))) else: ResultType.ok(( blck: consensusFork.MaybeBlindedBeaconBlock( diff --git a/beacon_chain/validators/message_router.nim b/beacon_chain/validators/message_router.nim index 5cde690eb8..4321f4d2b3 100644 --- a/beacon_chain/validators/message_router.nim +++ b/beacon_chain/validators/message_router.nim @@ -112,7 +112,9 @@ proc routeSignedBeaconBlock*( signature = shortLog(blck.signature), error = res.error() return err($(res.error()[1])) - when typeof(blck).kind >= ConsensusFork.Deneb: + when typeof(blck).kind == ConsensusFork.Fulu: + discard + elif typeof(blck).kind >= ConsensusFork.Deneb: if blobsOpt.isSome: let blobs = blobsOpt.get() let kzgCommits = blck.message.body.blob_kzg_commitments.asSeq diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index d8633e46c3..39efdbe720 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -37,7 +37,9 @@ macro copyFields*( # unblinded objects, and can't simply be copied. "transactions_root", "execution_payload", "execution_payload_header", "body", "withdrawals_root", - "deposit_requests_root", "withdrawal_requests_root", + "parent_block_hash", "parent_block_root", "builder_index", + "value", "blob_kzg_commitments_root","payload_attestations", + "deposit_requests_root", "withdrawal_requests_root", "slot", "consolidation_requests_root"]: # TODO use stew/assign2 result.add newAssignment( @@ -104,11 +106,17 @@ proc unblindAndRouteBlockMEV*( $response.contentType & " and content " & $response.data) template execution_payload: untyped = bundle.data.execution_payload - - if hash_tree_root(blindedBlock.message.body.execution_payload_header) != - hash_tree_root(execution_payload): - return err("unblinded payload doesn't match blinded payload header: " & - $blindedBlock.message.body.execution_payload_header) + + when blindedBlock is fulu_mev.SignedBlindedBeaconBlock: + if hash_tree_root(blindedBlock.message.body.signed_execution_payload_header.message) != + hash_tree_root(execution_payload): + return err("unblinded payload doesn't match blinded payload header: " & + $blindedBlock.message.body.signed_execution_payload_header) + else: + if hash_tree_root(blindedBlock.message.body.execution_payload_header) != + hash_tree_root(execution_payload): + return err("unblinded payload doesn't match blinded payload header: " & + $blindedBlock.message.body.execution_payload_header) # Signature provided is consistent with unblinded execution payload, # so construct full beacon block @@ -121,12 +129,19 @@ proc unblindAndRouteBlockMEV*( copyFields( signedBlock.message.body, blindedBlock.message.body, getFieldNames(typeof(signedBlock.message.body))) - assign(signedBlock.message.body.execution_payload, execution_payload) + when blindedBlock is fulu_mev.SignedBlindedBeaconBlock: + discard + else: + assign(signedBlock.message.body.execution_payload, bundle.data.execution_payload) + + # assign(signedBlock.message.body.execution_payload, execution_payload) signedBlock.root = hash_tree_root(signedBlock.message) doAssert signedBlock.root == hash_tree_root(blindedBlock.message) let blobsOpt = - when consensusFork >= ConsensusFork.Deneb: + when consensusFork >= ConsensusFork.Fulu: + Opt.none(seq[BlobSidecar]) + elif consensusFork >= ConsensusFork.Deneb: template blobs_bundle: untyped = bundle.data.blobs_bundle if blindedBlock.message.body.blob_kzg_commitments != bundle.data.blobs_bundle.commitments: From 25c31c205acecd7cca17a07bab57634c1398e927 Mon Sep 17 00:00:00 2001 From: Caleb Omoniyi <81532297+Tomi-3-0@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:35:36 +0000 Subject: [PATCH 3/4] update light client, beacon node, and testnet logic for epbs header compatibility --- beacon_chain/beacon_node_light_client.nim | 2 +- beacon_chain/nimbus_beacon_node.nim | 3 +- ncli/ncli_testnet.nim | 45 ++++++++++++----------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/beacon_chain/beacon_node_light_client.nim b/beacon_chain/beacon_node_light_client.nim index 4c1a7b6535..ab98dbcac3 100644 --- a/beacon_chain/beacon_node_light_client.nim +++ b/beacon_chain/beacon_node_light_client.nim @@ -42,7 +42,7 @@ proc initLightClient*( signedBlock: ForkedSignedBeaconBlock ): Future[void] {.async: (raises: [CancelledError]).} = withBlck(signedBlock): - when consensusFork >= ConsensusFork.Bellatrix: + when consensusFork >= ConsensusFork.Bellatrix and consensusFork != ConsensusFork.Fulu: if forkyBlck.message.is_execution_block: template payload(): auto = forkyBlck.message.body.execution_payload if not payload.block_hash.isZero: diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 1d6edc040c..b60111a16d 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -1576,7 +1576,8 @@ proc pruneBlobs(node: BeaconNode, slot: Slot) = for i in startIndex.. Date: Thu, 2 Jan 2025 09:50:33 +0000 Subject: [PATCH 4/4] Update copyright headers, execution payload signature tests, update toSignedBlindedBeaconBlock --- beacon_chain/beacon_chain_db_immutable.nim | 2 +- beacon_chain/beacon_node_light_client.nim | 2 +- .../blob_quarantine.nim | 2 +- .../consensus_object_pools/block_dag.nim | 2 +- .../consensus_object_pools/blockchain_dag.nim | 2 +- .../blockchain_list.nim | 2 +- .../data_column_quarantine.nim | 2 +- beacon_chain/el/el_manager.nim | 2 +- beacon_chain/el/engine_api_conversions.nim | 2 +- .../gossip_processing/block_processor.nim | 4 +- .../gossip_processing/gossip_validation.nim | 2 +- beacon_chain/nimbus_beacon_node.nim | 2 +- beacon_chain/nimbus_light_client.nim | 5 +- beacon_chain/spec/beacon_time.nim | 6 +- beacon_chain/spec/beaconstate.nim | 16 +-- beacon_chain/spec/datatypes/constants.nim | 5 +- beacon_chain/spec/datatypes/fulu.nim | 20 ++-- beacon_chain/spec/eip7594_helpers.nim | 2 +- beacon_chain/spec/eip7732_helpers.nim | 2 +- .../eth2_apis/eth2_rest_serialization.nim | 2 +- beacon_chain/spec/forks_light_client.nim | 2 +- beacon_chain/spec/helpers.nim | 2 +- beacon_chain/spec/helpers_el.nim | 2 +- beacon_chain/spec/mev/fulu_mev.nim | 13 +-- beacon_chain/spec/network.nim | 2 +- beacon_chain/spec/signatures.nim | 2 +- beacon_chain/spec/state_transition.nim | 2 +- beacon_chain/spec/state_transition_block.nim | 2 +- beacon_chain/sync/request_manager.nim | 2 +- beacon_chain/sync/sync_manager.nim | 2 +- beacon_chain/validators/beacon_validators.nim | 2 +- beacon_chain/validators/message_router.nim | 2 +- .../validators/message_router_mev.nim | 2 +- ncli/ncli_testnet.nim | 2 +- research/mev_mock.nim | 24 ++++- research/wss_sim.nim | 45 ++++---- tests/all_tests.nim | 4 +- .../test_fixture_fork_choice.nim | 46 ++++---- tests/test_message_signatures.nim | 102 +++++++++++++++++- tests/test_sync_manager.nim | 2 +- tests/test_toblindedblock.nim | 14 +-- 41 files changed, 234 insertions(+), 126 deletions(-) diff --git a/beacon_chain/beacon_chain_db_immutable.nim b/beacon_chain/beacon_chain_db_immutable.nim index bcf2b1caa1..ef7b006185 100644 --- a/beacon_chain/beacon_chain_db_immutable.nim +++ b/beacon_chain/beacon_chain_db_immutable.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2021-2024 Status Research & Development GmbH +# Copyright (c) 2021-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). diff --git a/beacon_chain/beacon_node_light_client.nim b/beacon_chain/beacon_node_light_client.nim index ab98dbcac3..e516dc6223 100644 --- a/beacon_chain/beacon_node_light_client.nim +++ b/beacon_chain/beacon_node_light_client.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-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). diff --git a/beacon_chain/consensus_object_pools/blob_quarantine.nim b/beacon_chain/consensus_object_pools/blob_quarantine.nim index 7deaf9d8d6..41b6861525 100644 --- a/beacon_chain/consensus_object_pools/blob_quarantine.nim +++ b/beacon_chain/consensus_object_pools/blob_quarantine.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/consensus_object_pools/block_dag.nim b/beacon_chain/consensus_object_pools/block_dag.nim index 4a81027ea0..34405ce8c5 100644 --- a/beacon_chain/consensus_object_pools/block_dag.nim +++ b/beacon_chain/consensus_object_pools/block_dag.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 8be0d99fac..05d639b4cf 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/consensus_object_pools/blockchain_list.nim b/beacon_chain/consensus_object_pools/blockchain_list.nim index 95a38249ec..5ae36b34f7 100644 --- a/beacon_chain/consensus_object_pools/blockchain_list.nim +++ b/beacon_chain/consensus_object_pools/blockchain_list.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/consensus_object_pools/data_column_quarantine.nim b/beacon_chain/consensus_object_pools/data_column_quarantine.nim index 20e9ed7846..2c22a53a23 100644 --- a/beacon_chain/consensus_object_pools/data_column_quarantine.nim +++ b/beacon_chain/consensus_object_pools/data_column_quarantine.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/el/el_manager.nim b/beacon_chain/el/el_manager.nim index 3f18b2e9e8..cfe6bc54ab 100644 --- a/beacon_chain/el/el_manager.nim +++ b/beacon_chain/el/el_manager.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/el/engine_api_conversions.nim b/beacon_chain/el/engine_api_conversions.nim index 1e7a9de05f..e940bd047c 100644 --- a/beacon_chain/el/engine_api_conversions.nim +++ b/beacon_chain/el/engine_api_conversions.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim index 4c93339cc7..7231ccdc7e 100644 --- a/beacon_chain/gossip_processing/block_processor.nim +++ b/beacon_chain/gossip_processing/block_processor.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -302,7 +302,7 @@ proc newExecutionPayload*( blck.body.execution_payload else: debug "Block body does not support execution payload" - nil + default(electra.ExecutionPayload) if not elManager.hasProperlyConfiguredConnection: if elManager.hasConnection: diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 90cb46eac2..4207c213e7 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2019-2024 Status Research & Development GmbH +# Copyright (c) 2019-2025 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index b60111a16d..80f8c801db 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/nimbus_light_client.nim b/beacon_chain/nimbus_light_client.nim index 519b1e89db..2d2a5403f9 100644 --- a/beacon_chain/nimbus_light_client.nim +++ b/beacon_chain/nimbus_light_client.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-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). @@ -111,7 +111,8 @@ programMain: signedBlock: ForkedSignedBeaconBlock ): Future[void] {.async: (raises: [CancelledError]).} = withBlck(signedBlock): - when consensusFork >= ConsensusFork.Bellatrix: + when consensusFork >= ConsensusFork.Bellatrix and + consensusFork < ConsensusFork.Fulu: if forkyBlck.message.is_execution_block: template payload(): auto = forkyBlck.message.body.execution_payload if elManager != nil and not payload.block_hash.isZero: diff --git a/beacon_chain/spec/beacon_time.nim b/beacon_chain/spec/beacon_time.nim index aac8082ab7..a3eb91b796 100644 --- a/beacon_chain/spec/beacon_time.nim +++ b/beacon_chain/spec/beacon_time.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -43,8 +43,10 @@ const GENESIS_SLOT* = Slot(0) GENESIS_EPOCH* = Epoch(0) # compute_epoch_at_slot(GENESIS_SLOT) + INTERVALS_PER_SLOT* = 3 + # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/fork-choice.md#constants - INTERVALS_PER_SLOT* = 4 + INTERVALS_PER_SLOT_EIP7732* = 4 FAR_FUTURE_BEACON_TIME* = BeaconTime(ns_since_genesis: int64.high()) diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index fe10a90fd4..eb99b909f5 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -2108,13 +2108,17 @@ func upgrade_to_electra*( blob_gas_used: pre.latest_execution_payload_header.blob_gas_used, excess_blob_gas: pre.latest_execution_payload_header.excess_blob_gas) - var earliest_exit_epoch = - compute_activation_exit_epoch(get_current_epoch(pre)) + var max_exit_epoch = FAR_FUTURE_EPOCH for v in pre.validators: if v.exit_epoch != FAR_FUTURE_EPOCH: - if v.exit_epoch > earliest_exit_epoch: - earliest_exit_epoch = v.exit_epoch - earliest_exit_epoch += 1 + max_exit_epoch = + if max_exit_epoch == FAR_FUTURE_EPOCH: + v.exit_epoch + else: + max(max_exit_epoch, v.exit_epoch) + if max_exit_epoch == FAR_FUTURE_EPOCH: + max_exit_epoch = get_current_epoch(pre) + let earliest_exit_epoch = max_exit_epoch + 1 let post = (ref electra.BeaconState)( # Versioning diff --git a/beacon_chain/spec/datatypes/constants.nim b/beacon_chain/spec/datatypes/constants.nim index d96647a069..c3306c6eea 100644 --- a/beacon_chain/spec/datatypes/constants.nim +++ b/beacon_chain/spec/datatypes/constants.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-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). @@ -68,6 +68,9 @@ const REORG_PARENT_WEIGHT_THRESHOLD*: uint64 = 160 REORG_MAX_EPOCHS_SINCE_FINALIZATION* = Epoch(2) + #https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/fork-choice.md#constants + PROPOSER_SCORE_BOOST_EIP7732*: uint64 = 20 + # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.7/specs/deneb/p2p-interface.md#configuration BLOB_SIDECAR_SUBNET_COUNT*: uint64 = 6 diff --git a/beacon_chain/spec/datatypes/fulu.nim b/beacon_chain/spec/datatypes/fulu.nim index df385a529a..0eeeac47f8 100644 --- a/beacon_chain/spec/datatypes/fulu.nim +++ b/beacon_chain/spec/datatypes/fulu.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). @@ -20,7 +20,6 @@ import ssz_serialization/[merkleization, proofs], ssz_serialization/types as sszTypes, kzg4844/[kzg, kzg_abi], - std/typetraits, chronicles, ../digest, "."/[base, phase0, electra], @@ -36,11 +35,6 @@ from ./capella import ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChangeList, Withdrawal, EXECUTION_PAYLOAD_GINDEX from ./deneb import Blobs, BlobsBundle, KzgCommitments, KzgProofs -from ./electra import - PendingDeposit, PendingPartialWithdrawal, - PendingConsolidation, ExecutionPayload, Attestation, TrustedAttestation, - ElectraCommitteeValidatorsBits,AttesterSlashing, AttestationCommitteeBits, - FINALIZED_ROOT_GINDEX_ELECTRA, ExecutionRequests export json_serialization, base, kzg4844 @@ -130,16 +124,16 @@ const PTC_SIZE* = 512 # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/_features/eip7732/beacon-chain.md#max-operations-per-block - MAX_PAYLOAD_ATTESTATIONS = 4 + MAX_PAYLOAD_ATTESTATIONS* = 4 type PTCStatus* = distinct uint64 # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payloadattestationdata PayloadAttestationData* = object - beaconBlockRoot*: Eth2Digest + beacon_block_root*: Eth2Digest slot*: Slot - payload_Status*: uint8 + payload_status*: uint8 # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#payloadattestation PayloadAttestation* = object @@ -155,7 +149,7 @@ type # https://github.com/ethereum/consensus-specs/blob/dev/specs/_features/eip7732/beacon-chain.md#indexedpayloadattestation IndexedPayloadAttestation* = object - attestingIndices*: List[ValidatorIndex, Limit PTC_SIZE] + attesting_indices*: List[ValidatorIndex, Limit PTC_SIZE] data*: PayloadAttestationData signature*: ValidatorSig @@ -630,9 +624,9 @@ func initHashedBeaconState*(s: BeaconState): HashedBeaconState = func shortLog*(v: PayloadAttestationData): auto = ( - beaconBlockRoot: shortLog(v.beaconBlockRoot), + beacon_block_root: shortLog(v.beacon_block_root), slot: shortLog(v.slot), - payload_Status: $v.payload_Status + payload_status: $v.payload_status ) func shortLog*(v: SomeBeaconBlock): auto = diff --git a/beacon_chain/spec/eip7594_helpers.nim b/beacon_chain/spec/eip7594_helpers.nim index 036d258752..1bfd4378da 100644 --- a/beacon_chain/spec/eip7594_helpers.nim +++ b/beacon_chain/spec/eip7594_helpers.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/eip7732_helpers.nim b/beacon_chain/spec/eip7732_helpers.nim index c562eca0fa..3e28db1c0e 100644 --- a/beacon_chain/spec/eip7732_helpers.nim +++ b/beacon_chain/spec/eip7732_helpers.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index e5fde6b128..7ee3dc5fc3 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/forks_light_client.nim b/beacon_chain/spec/forks_light_client.nim index e664bcec37..a8cf90e0d6 100644 --- a/beacon_chain/spec/forks_light_client.nim +++ b/beacon_chain/spec/forks_light_client.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2023-2024 Status Research & Development GmbH +# Copyright (c) 2023-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). diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 7ec080ffe8..26f3c8b7ce 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/helpers_el.nim b/beacon_chain/spec/helpers_el.nim index 5451cd14b0..1e0dbff1b6 100644 --- a/beacon_chain/spec/helpers_el.nim +++ b/beacon_chain/spec/helpers_el.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). diff --git a/beacon_chain/spec/mev/fulu_mev.nim b/beacon_chain/spec/mev/fulu_mev.nim index 0ddc9d976a..7b72442eb2 100644 --- a/beacon_chain/spec/mev/fulu_mev.nim +++ b/beacon_chain/spec/mev/fulu_mev.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). @@ -40,10 +40,10 @@ type deposits*: List[Deposit, Limit MAX_DEPOSITS] voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS] sync_aggregate*: SyncAggregate - signed_execution_payload_header*: SignedExecutionPayloadHeader bls_to_execution_changes*: - List[SignedBLSToExecutionChange, - Limit MAX_BLS_TO_EXECUTION_CHANGES] + List[SignedBLSToExecutionChange, Limit MAX_BLS_TO_EXECUTION_CHANGES] + signed_execution_payload_header*: SignedExecutionPayloadHeader + payload_attestations*: List[PayloadAttestation, Limit MAX_PAYLOAD_ATTESTATIONS] # blob_kzg_commitments*: KzgCommitments # [New in Deneb] # execution_requests*: ExecutionRequests # [New in Electra] @@ -124,6 +124,7 @@ func toSignedBlindedBeaconBlock*(blck: fulu.SignedBeaconBlock): deposits: blck.message.body.deposits, voluntary_exits: blck.message.body.voluntary_exits, sync_aggregate: blck.message.body.sync_aggregate, + bls_to_execution_changes: blck.message.body.bls_to_execution_changes, signed_execution_payload_header: SignedExecutionPayloadHeader( message: ExecutionPayloadHeader( parent_block_hash: blck.message.body.signed_execution_payload_header.message.parent_block_hash, @@ -135,6 +136,6 @@ func toSignedBlindedBeaconBlock*(blck: fulu.SignedBeaconBlock): blob_kzg_commitments_root: blck.message.body.signed_execution_payload_header.message.blob_kzg_commitments_root, block_hash: blck.message.body.signed_execution_payload_header.message.block_hash ), - signature: blck.signature), - bls_to_execution_changes: blck.message.body.bls_to_execution_changes)), + signature: blck.message.body.signed_execution_payload_header.signature), + payload_attestations: blck.message.body.payload_attestations)), signature: blck.signature) diff --git a/beacon_chain/spec/network.nim b/beacon_chain/spec/network.nim index f6564dc4fc..91b7ab0f41 100644 --- a/beacon_chain/spec/network.nim +++ b/beacon_chain/spec/network.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/signatures.nim b/beacon_chain/spec/signatures.nim index b2ab7a0997..10c5340951 100644 --- a/beacon_chain/spec/signatures.nim +++ b/beacon_chain/spec/signatures.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index c0d30d0694..1e8d0e5fd5 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index 3e91f3f92a..80c6f05a37 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/sync/request_manager.nim b/beacon_chain/sync/request_manager.nim index f35d7e030b..608d7e9d5f 100644 --- a/beacon_chain/sync/request_manager.nim +++ b/beacon_chain/sync/request_manager.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/sync/sync_manager.nim b/beacon_chain/sync/sync_manager.nim index 77be5bd511..e15f161697 100644 --- a/beacon_chain/sync/sync_manager.nim +++ b/beacon_chain/sync/sync_manager.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index 953c1cb695..b9f0b8c383 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/validators/message_router.nim b/beacon_chain/validators/message_router.nim index 4321f4d2b3..f0b402b744 100644 --- a/beacon_chain/validators/message_router.nim +++ b/beacon_chain/validators/message_router.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index 39efdbe720..43dff7e115 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-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). diff --git a/ncli/ncli_testnet.nim b/ncli/ncli_testnet.nim index d0367b9ff7..ec8b6bb651 100644 --- a/ncli/ncli_testnet.nim +++ b/ncli/ncli_testnet.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). diff --git a/research/mev_mock.nim b/research/mev_mock.nim index be9840babc..9f0d148164 100644 --- a/research/mev_mock.nim +++ b/research/mev_mock.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2023-2024 Status Research & Development GmbH +# Copyright (c) 2023-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). @@ -50,13 +50,27 @@ proc getParentBlock(restClient: RestClientRef): return Opt.none ParentHeaderInfo withBlck(resp): - when consensusFork >= ConsensusFork.Capella: - return Opt.some ParentHeaderInfo( - block_number: forkyBlck.message.body.execution_payload.block_number, - timestamp: forkyBlck.message.body.execution_payload.timestamp) + when consensusFork >= ConsensusFork.Capella and + consensusFork < ConsensusFork.Fulu: + return Opt.some ParentHeaderInfo( + block_number: forkyBlck.message.body.execution_payload.block_number, + timestamp: forkyBlck.message.body.execution_payload.timestamp) else: discard + # TODO {what is expected of eip-7732 blocks} + # withBlck(resp): + # when consensusFork >= ConsensusFork.Fulu: + # return Opt.some ParentHeaderInfo( + # block_: forkyBlck.message.body.signed_execution_payload_header.message, + # timestamp: forkyBlck.message.signed_execution_payload_header.message) + # elif consensusFork >= ConsensusFork.Capella: + # return Opt.some ParentHeaderInfo( + # block_number: forkyBlck.message.body.execution_payload.block_number, + # timestamp: forkyBlck.message.body.execution_payload.timestamp) + # else: + # discard + proc getWithdrawals(restClient: RestClientRef): Future[Opt[seq[Withdrawal]]] {.async.} = let resp: RestResponse[rest_types.GetNextWithdrawalsResponse] = diff --git a/research/wss_sim.nim b/research/wss_sim.nim index 72fddb6b75..7b8219556f 100644 --- a/research/wss_sim.nim +++ b/research/wss_sim.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2022-2024 Status Research & Development GmbH +# Copyright (c) 2022-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). @@ -119,27 +119,28 @@ cli do(validatorsDir: string, secretsDir: string, # The EL may otherwise refuse to produce new heads elManager.start(syncChain = false) withBlck(blck[]): - when consensusFork >= ConsensusFork.Bellatrix: - if forkyBlck.message.is_execution_block: - template payload(): auto = forkyBlck.message.body.execution_payload - if not payload.block_hash.isZero: - notice "Syncing EL", elUrl, jwtSecret - while true: - waitFor noCancel sleepAsync(chronos.seconds(2)) - (waitFor noCancel elManager - .newExecutionPayload(forkyBlck.message)).isOkOr: - continue - - let (status, _) = waitFor noCancel elManager.forkchoiceUpdated( - headBlockHash = payload.block_hash, - safeBlockHash = payload.block_hash, - finalizedBlockHash = ZERO_HASH, - payloadAttributes = Opt.none(consensusFork.PayloadAttributes)) - if status != PayloadExecutionStatus.valid: - continue - - notice "EL synced", elUrl, jwtSecret - break + when consensusFork >= ConsensusFork.Bellatrix and + consensusFork < ConsensusFork.Fulu: + if forkyBlck.message.is_execution_block: + template payload(): auto = forkyBlck.message.body.execution_payload + if not payload.block_hash.isZero: + notice "Syncing EL", elUrl, jwtSecret + while true: + waitFor noCancel sleepAsync(chronos.seconds(2)) + (waitFor noCancel elManager + .newExecutionPayload(forkyBlck.message)).isOkOr: + continue + + let (status, _) = waitFor noCancel elManager.forkchoiceUpdated( + headBlockHash = payload.block_hash, + safeBlockHash = payload.block_hash, + finalizedBlockHash = ZERO_HASH, + payloadAttributes = Opt.none(consensusFork.PayloadAttributes)) + if status != PayloadExecutionStatus.valid: + continue + + notice "EL synced", elUrl, jwtSecret + break var clock = BeaconClock.init(getStateField(state[], genesis_time)).valueOr: diff --git a/tests/all_tests.nim b/tests/all_tests.nim index c9df26277b..28ac4d1e4d 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -7,7 +7,7 @@ {.push raises: [].} -# All tests except scenarios, which as compiled separately for mainnet and minimal +# All tests except scenarios, which is compiled separately for mainnet and minimal import ./testutil diff --git a/tests/consensus_spec/test_fixture_fork_choice.nim b/tests/consensus_spec/test_fixture_fork_choice.nim index b735640c36..88e865028b 100644 --- a/tests/consensus_spec/test_fixture_fork_choice.nim +++ b/tests/consensus_spec/test_fixture_fork_choice.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -182,12 +182,13 @@ proc stepOnBlock( invalidatedHashes: Table[Eth2Digest, Eth2Digest]): Result[BlockRef, VerifierError] = # 1. Validate blobs - when typeof(signedBlock).kind >= ConsensusFork.Deneb: - let kzgCommits = signedBlock.message.body.blob_kzg_commitments.asSeq - if kzgCommits.len > 0 or blobData.isSome: - if blobData.isNone or kzgCommits.validate_blobs( - blobData.get.blobs, blobData.get.proofs).isErr: - return err(VerifierError.Invalid) + when typeof(signedBlock).kind >= ConsensusFork.Deneb and + typeof(signedBlock).kind < ConsensusFork.Fulu: + let kzgCommits = signedBlock.message.body.blob_kzg_commitments.asSeq + if kzgCommits.len > 0 or blobData.isSome: + if blobData.isNone or kzgCommits.validate_blobs( + blobData.get.blobs, blobData.get.proofs).isErr: + return err(VerifierError.Invalid) else: doAssert blobData.isNone, "Pre-Deneb test with specified blob data" @@ -209,21 +210,22 @@ proc stepOnBlock( # this wouldn't be part of this check, presumably, their FC test vector step # would also have `true` validity because it'd not be known they weren't, so # adding this mock of the block processor is realistic and sufficient. - when consensusFork >= ConsensusFork.Bellatrix: - let executionBlockHash = - signedBlock.message.body.execution_payload.block_hash - if executionBlockHash in invalidatedHashes: - # Mocks fork choice INVALID list application. These tests sequence this - # in a way the block processor does not, specifying each payload_status - # before the block itself, while Nimbus fork choice treats invalidating - # a non-existent block root as a no-op and does not remember it for the - # future. - let lvh = invalidatedHashes.getOrDefault( - executionBlockHash, static(default(Eth2Digest))) - fkChoice[].mark_root_invalid(dag.getEarliestInvalidBlockRoot( - signedBlock.message.parent_root, lvh, executionBlockHash)) - - return err VerifierError.Invalid + when consensusFork >= ConsensusFork.Bellatrix and + consensusFork < ConsensusFork.Fulu: + let executionBlockHash = + signedBlock.message.body.execution_payload.block_hash + if executionBlockHash in invalidatedHashes: + # Mocks fork choice INVALID list application. These tests sequence this + # in a way the block processor does not, specifying each payload_status + # before the block itself, while Nimbus fork choice treats invalidating + # a non-existent block root as a no-op and does not remember it for the + # future. + let lvh = invalidatedHashes.getOrDefault( + executionBlockHash, static(default(Eth2Digest))) + fkChoice[].mark_root_invalid(dag.getEarliestInvalidBlockRoot( + signedBlock.message.parent_root, lvh, executionBlockHash)) + + return err VerifierError.Invalid let blockAdded = dag.addHeadBlock(verifier, signedBlock) do ( blckRef: BlockRef, signedBlock: consensusFork.TrustedSignedBeaconBlock, diff --git a/tests/test_message_signatures.nim b/tests/test_message_signatures.nim index 86ade21ba8..d21f91c4be 100644 --- a/tests/test_message_signatures.nim +++ b/tests/test_message_signatures.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2018-2024 Status Research & Development GmbH +# Copyright (c) 2018-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). @@ -272,4 +272,102 @@ suite "Message signatures": fork0, genesis_validators_root0, slot, subcommittee_index, load(pubkey0).get, get_sync_committee_selection_proof( fork0, genesis_validators_root1, slot, - subcommittee_index, privkey0).toValidatorSig) \ No newline at end of file + subcommittee_index, privkey0).toValidatorSig) + + let testState = default(fulu.BeaconState) + + test "execution payload header signatures": + let msg = default(fulu.SignedExecutionPayloadHeader) + + check: + # Matching public/private keys and genesis validator roots + verify_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, + testState, privkey0).toValidatorSig) + + # Mismatched public/private keys + not verify_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, + testState, privkey1).toValidatorSig) + + # Mismatched forks + not verify_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_header_signature( + fork1, genesis_validators_root0, msg, + testState, privkey0).toValidatorSig) + + # Mismatched genesis validator roots + not verify_execution_payload_header_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_header_signature( + fork0, genesis_validators_root1, msg, + testState, privkey0).toValidatorSig) + + test "execution payload envelope signatures": + let msg = default(fulu.SignedExecutionPayloadEnvelope) + + check: + # Matching public/private keys and genesis validator roots + verify_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, + testState, privkey0).toValidatorSig) + + # Mismatched public/private keys + not verify_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, + testState, privkey1).toValidatorSig) + + # Mismatched forks + not verify_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_envelope_signature( + fork1, genesis_validators_root0, msg, + testState, privkey0).toValidatorSig) + + # Mismatched genesis validator roots + not verify_execution_payload_envelope_signature( + fork0, genesis_validators_root0, msg, testState, + load(pubkey0).get, get_execution_payload_envelope_signature( + fork0, genesis_validators_root1, msg, + testState, privkey0).toValidatorSig) + + test "execution payload attestation signatures": + let attestation = default(fulu.PayloadAttestationMessage) + + check: + # Matching public/private keys and genesis validator roots + verify_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, testState, + load(pubkey0).get, get_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, + testState, privkey0).toValidatorSig) + + # Mismatched public/private keys + not verify_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, testState, + load(pubkey0).get, get_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, + testState, privkey1).toValidatorSig) + + # Mismatched forks + not verify_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, testState, + load(pubkey0).get, get_payload_attestation_message_signature( + fork1, genesis_validators_root0, attestation, + testState, privkey0).toValidatorSig) + + # Mismatched genesis validator roots + not verify_payload_attestation_message_signature( + fork0, genesis_validators_root0, attestation, testState, + load(pubkey0).get, get_payload_attestation_message_signature( + fork0, genesis_validators_root1, attestation, + testState, privkey0).toValidatorSig) \ No newline at end of file diff --git a/tests/test_sync_manager.nim b/tests/test_sync_manager.nim index 256e9c2af4..531693cfa7 100644 --- a/tests/test_sync_manager.nim +++ b/tests/test_sync_manager.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2020-2024 Status Research & Development GmbH +# Copyright (c) 2020-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). diff --git a/tests/test_toblindedblock.nim b/tests/test_toblindedblock.nim index ed7768ea2f..b0736631bc 100644 --- a/tests/test_toblindedblock.nim +++ b/tests/test_toblindedblock.nim @@ -1,5 +1,5 @@ # beacon_chain -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2024-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). @@ -136,18 +136,6 @@ template electra_steps() = default(ConsolidationRequest)) do_check -template fulu_steps() = - when consensusFork < ConsensusFork.Fulu: - check: b.message.body.execution_requests.deposits.add( - default(DepositRequest)) - do_check - check: b.message.body.execution_requests.withdrawals.add( - default(WithdrawalRequest)) - do_check - check: b.message.body.execution_requests.consolidations.add( - default(ConsolidationRequest)) - do_check - suite "Blinded block conversions": withAll(ConsensusFork): when consensusFork >= ConsensusFork.Bellatrix: