From 15a049ba24c004e133c3db799fc1e43ec62747d7 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 22 Jul 2024 13:05:20 +0200 Subject: [PATCH] Adopt EIP-7688: Forward compatible consensus data structures EIP-4788 exposes the beacon root to smart contracts, but smart contracts using it need to be redeployed / upgraded whenever the indexing changes during a fork, even if that fork does not touch any used functionality. This problem expands further to bridges on other blockchains, or even into wallet apps on a phone that verify data from the beacon chain instead of trusting the server. It is quite unrealistic to expect such projects to all align their release cadence with Ethereum's forks. EIP-7688 fixes this by defining forward compatibility for beacon chain data structures. Electra `Profile` retain their Merkleization even when rebased to `StableContainer` definitions from future forks, enabling decentralized protocols to drop the requirement for trusted parties to periodically upgrade beacon state proof verifiers. --- presets/mainnet/electra.yaml | 5 + presets/minimal/electra.yaml | 5 + pysetup/spec_builders/deneb.py | 5 - pysetup/spec_builders/electra.py | 14 +- setup.py | 2 +- specs/capella/light-client/full-node.md | 2 +- specs/capella/light-client/sync-protocol.md | 16 +- specs/deneb/light-client/full-node.md | 2 +- specs/deneb/light-client/sync-protocol.md | 5 +- specs/deneb/p2p-interface.md | 15 +- specs/electra/beacon-chain.md | 309 ++++++++++++++++-- specs/electra/fork.md | 20 +- specs/electra/light-client/fork.md | 23 +- specs/electra/light-client/full-node.md | 2 +- specs/electra/light-client/sync-protocol.md | 30 +- specs/electra/p2p-interface.md | 22 ++ .../light_client/test_single_merkle_proof.py | 5 +- .../deneb/unittests/test_config_invariants.py | 8 +- .../eth2spec/test/helpers/electra/fork.py | 4 +- .../eth2spec/test/helpers/light_client.py | 6 + 20 files changed, 436 insertions(+), 64 deletions(-) diff --git a/presets/mainnet/electra.yaml b/presets/mainnet/electra.yaml index bb97cfa445..77e618d929 100644 --- a/presets/mainnet/electra.yaml +++ b/presets/mainnet/electra.yaml @@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 # --------------------------------------------------------------- # 2**3 ( = 8) pending withdrawals MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 + +# Misc +# --------------------------------------------------------------- +# `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 12 = 20 +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 20 diff --git a/presets/minimal/electra.yaml b/presets/minimal/electra.yaml index ef1ce494d8..5ca228d32b 100644 --- a/presets/minimal/electra.yaml +++ b/presets/minimal/electra.yaml @@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2 # --------------------------------------------------------------- # 2**0 ( = 1) pending withdrawals MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 1 + +# Misc +# --------------------------------------------------------------- +# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 4 = 12 +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 12 diff --git a/pysetup/spec_builders/deneb.py b/pysetup/spec_builders/deneb.py index 436ae70b1d..7585ee31dc 100644 --- a/pysetup/spec_builders/deneb.py +++ b/pysetup/spec_builders/deneb.py @@ -72,10 +72,5 @@ def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]: 'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value, 'MAX_BLOBS_PER_BLOCK': spec_object.config_vars['MAX_BLOBS_PER_BLOCK'].value, 'MAX_BLOB_COMMITMENTS_PER_BLOCK': spec_object.preset_vars['MAX_BLOB_COMMITMENTS_PER_BLOCK'].value, - } - - @classmethod - def hardcoded_func_dep_presets(cls, spec_object) -> Dict[str, str]: - return { 'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH': spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH'].value, } diff --git a/pysetup/spec_builders/electra.py b/pysetup/spec_builders/electra.py index 2ab1f5ecfb..594516a0e8 100644 --- a/pysetup/spec_builders/electra.py +++ b/pysetup/spec_builders/electra.py @@ -10,14 +10,22 @@ class ElectraSpecBuilder(BaseSpecBuilder): def imports(cls, preset_name: str): return f''' from eth2spec.deneb import {preset_name} as deneb +from eth2spec.utils.ssz.ssz_typing import StableContainer, Profile ''' @classmethod def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: return { - 'FINALIZED_ROOT_GINDEX_ELECTRA': 'GeneralizedIndex(169)', - 'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(86)', - 'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(87)', + 'FINALIZED_ROOT_GINDEX_ELECTRA': 'GeneralizedIndex(553)', + 'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(278)', + 'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(279)', + 'EXECUTION_PAYLOAD_GINDEX_ELECTRA': 'GeneralizedIndex(137)', + } + + @classmethod + def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]: + return { + 'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA': spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA'].value, } diff --git a/setup.py b/setup.py index 790384a098..e1cd15ceba 100644 --- a/setup.py +++ b/setup.py @@ -556,7 +556,7 @@ def run(self): "pycryptodome>=3.19.1", "py_ecc==6.0.0", "milagro_bls_binding==1.9.0", - "remerkleable==0.1.28", + "remerkleable @ git+https://github.com/etan-status/remerkleable@dev/etan/sc-default", "trie==2.0.2", RUAMEL_YAML_VERSION, "lru-dict==1.2.0", diff --git a/specs/capella/light-client/full-node.md b/specs/capella/light-client/full-node.md index 319fb1c944..914b3529a4 100644 --- a/specs/capella/light-client/full-node.md +++ b/specs/capella/light-client/full-node.md @@ -47,7 +47,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: withdrawals_root=hash_tree_root(payload.withdrawals), ) execution_branch = ExecutionBranch( - compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)) + compute_merkle_proof(block.message.body, execution_payload_gindex_at_slot(block.message.slot))) else: # Note that during fork transitions, `finalized_header` may still point to earlier forks. # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md index b241b21378..888bc89041 100644 --- a/specs/capella/light-client/sync-protocol.md +++ b/specs/capella/light-client/sync-protocol.md @@ -14,6 +14,7 @@ - [Containers](#containers) - [Modified `LightClientHeader`](#modified-lightclientheader) - [Helper functions](#helper-functions) + - [`execution_payload_gindex_at_slot`](#execution_payload_gindex_at_slot) - [`get_lc_execution_root`](#get_lc_execution_root) - [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header) @@ -55,6 +56,16 @@ class LightClientHeader(Container): ## Helper functions +### `execution_payload_gindex_at_slot` + +```python +def execution_payload_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + assert epoch >= CAPELLA_FORK_EPOCH + + return EXECUTION_PAYLOAD_GINDEX +``` + ### `get_lc_execution_root` ```python @@ -79,11 +90,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool: and header.execution_branch == ExecutionBranch() ) - return is_valid_merkle_branch( + return is_valid_normalized_merkle_branch( leaf=get_lc_execution_root(header), branch=header.execution_branch, - depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), - index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + gindex=execution_payload_gindex_at_slot(header.beacon.slot), root=header.beacon.body_root, ) ``` diff --git a/specs/deneb/light-client/full-node.md b/specs/deneb/light-client/full-node.md index 424723667c..141a2edeb3 100644 --- a/specs/deneb/light-client/full-node.md +++ b/specs/deneb/light-client/full-node.md @@ -53,7 +53,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: execution_header.excess_blob_gas = payload.excess_blob_gas execution_branch = ExecutionBranch( - compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)) + compute_merkle_proof(block.message.body, execution_payload_gindex_at_slot(block.message.slot))) else: # Note that during fork transitions, `finalized_header` may still point to earlier forks. # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), diff --git a/specs/deneb/light-client/sync-protocol.md b/specs/deneb/light-client/sync-protocol.md index 38aa3897b3..8a33e12346 100644 --- a/specs/deneb/light-client/sync-protocol.md +++ b/specs/deneb/light-client/sync-protocol.md @@ -77,11 +77,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool: and header.execution_branch == ExecutionBranch() ) - return is_valid_merkle_branch( + return is_valid_normalized_merkle_branch( leaf=get_lc_execution_root(header), branch=header.execution_branch, - depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), - index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + gindex=execution_payload_gindex_at_slot(header.beacon.slot), root=header.beacon.body_root, ) ``` diff --git a/specs/deneb/p2p-interface.md b/specs/deneb/p2p-interface.md index 41e2fa9d3f..f2553fd7a9 100644 --- a/specs/deneb/p2p-interface.md +++ b/specs/deneb/p2p-interface.md @@ -14,6 +14,7 @@ The specification of these changes continues in the same format as the network s - [Constant](#constant) - [Preset](#preset) - [Configuration](#configuration) + - [Custom types](#custom-types) - [Containers](#containers) - [`BlobSidecar`](#blobsidecar) - [`BlobIdentifier`](#blobidentifier) @@ -66,6 +67,12 @@ The specification of these changes continues in the same format as the network s | `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` | `2**12` (= 4096 epochs, ~18 days) | The minimum epoch range over which a node must serve blob sidecars | | `BLOB_SIDECAR_SUBNET_COUNT` | `6` | The number of blob sidecar subnets used in the gossipsub protocol. | +### Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `KZGCommitmentInclusionProof` | `Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH]` | Merkle branch of a single `blob_kzg_commitments` list item within `BeaconBlockBody` | + ### Containers #### `BlobSidecar` @@ -79,7 +86,7 @@ class BlobSidecar(Container): kzg_commitment: KZGCommitment kzg_proof: KZGProof # Allows for quick verification of kzg_commitment signed_block_header: SignedBeaconBlockHeader - kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH] + kzg_commitment_inclusion_proof: KZGCommitmentInclusionProof ``` #### `BlobIdentifier` @@ -98,12 +105,12 @@ class BlobIdentifier(Container): ```python def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool: - gindex = get_subtree_index(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments', blob_sidecar.index)) + gindex = get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments', blob_sidecar.index) return is_valid_merkle_branch( leaf=blob_sidecar.kzg_commitment.hash_tree_root(), branch=blob_sidecar.kzg_commitment_inclusion_proof, - depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, - index=gindex, + depth=floorlog2(gindex), + index=get_subtree_index(gindex), root=blob_sidecar.signed_block_header.message.body_root, ) ``` diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index dbf84d8de8..be9cce2427 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -10,6 +10,7 @@ - [Introduction](#introduction) - [Constants](#constants) + - [`StableContainer` capacities](#stablecontainer-capacities) - [Misc](#misc) - [Withdrawal prefixes](#withdrawal-prefixes) - [Preset](#preset) @@ -29,13 +30,24 @@ - [`WithdrawalRequest`](#withdrawalrequest) - [`ConsolidationRequest`](#consolidationrequest) - [`PendingConsolidation`](#pendingconsolidation) - - [`ExecutionRequests`](#executionrequests) + - [`StableContainer` definitions](#stablecontainer-definitions) + - [`StableAttestation`](#stableattestation) + - [`StableIndexedAttestation`](#stableindexedattestation) + - [`StableAttesterSlashing`](#stableattesterslashing) + - [`StableExecutionPayload`](#stableexecutionpayload) + - [`StableExecutionPayloadHeader`](#stableexecutionpayloadheader) + - [`StableExecutionRequests`](#stableexecutionrequests) + - [`StableBeaconBlockBody`](#stablebeaconblockbody) + - [`StableBeaconState`](#stablebeaconstate) - [Modified Containers](#modified-containers) - [`AttesterSlashing`](#attesterslashing) - - [`BeaconBlockBody`](#beaconblockbody) - - [Extended Containers](#extended-containers) + - [`Profile` definitions](#profile-definitions) - [`Attestation`](#attestation) - [`IndexedAttestation`](#indexedattestation) + - [`BeaconBlockBody`](#beaconblockbody) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`ExecutionRequests`](#executionrequests) - [`BeaconState`](#beaconstate) - [Helper functions](#helper-functions) - [Predicates](#predicates) @@ -121,6 +133,17 @@ Electra is a consensus-layer upgrade containing a number of features. Including: The following values are (non-configurable) constants used throughout the specification. +### `StableContainer` capacities + +| Name | Value | Description | +| - | - | - | +| `MAX_ATTESTATION_FIELDS` | `uint64(2**3)` (= 8) | Maximum number of fields to which `StableAttestation` can ever grow in the future | +| `MAX_INDEXED_ATTESTATION_FIELDS` | `uint64(2**3)` (= 8) | Maximum number of fields to which `StableIndexedAttestation` can ever grow in the future | +| `MAX_EXECUTION_PAYLOAD_FIELDS` | `uint64(2**6)` (= 64) | Maximum number of fields to which `StableExecutionPayload` can ever grow in the future | +| `MAX_EXECUTION_REQUESTS_FIELDS` | `uint64(2**4)` (= 16) | Maximum number of fields to which `StableExecutionRequests` can ever grow in the future | +| `MAX_BEACON_BLOCK_BODY_FIELDS` | `uint64(2**6)` (= 64) | Maximum number of fields to which `StableBeaconBlockBody` can ever grow in the future | +| `MAX_BEACON_STATE_FIELDS` | `uint64(2**7)` (= 128) | Maximum number of fields to which `StableBeaconState` can ever grow in the future | + ### Misc | Name | Value | Description | @@ -225,6 +248,7 @@ class PendingPartialWithdrawal(Container): amount: Gwei withdrawable_epoch: Epoch ``` + #### `WithdrawalRequest` *Note*: The container is new in EIP7251:EIP7002. @@ -257,17 +281,189 @@ class PendingConsolidation(Container): target_index: ValidatorIndex ``` -#### `ExecutionRequests` +### `StableContainer` definitions + +These definitions provide EIP-7495 forward-compatibility guarantees. `Profile` based on these `StableContainer` definitions retain their Merkleization when rebased to `StableContainer` definitions of future forks. + +#### `StableAttestation` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableAttestation(StableContainer[MAX_ATTESTATION_FIELDS]): + aggregation_bits: Optional[Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]] + data: Optional[AttestationData] + signature: Optional[BLSSignature] + committee_bits: Optional[Bitvector[MAX_COMMITTEES_PER_SLOT]] +``` + +#### `StableIndexedAttestation` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableIndexedAttestation(StableContainer[MAX_INDEXED_ATTESTATION_FIELDS]): + attesting_indices: Optional[List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]] + data: Optional[AttestationData] + signature: Optional[BLSSignature] +``` + +#### `StableAttesterSlashing` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableAttesterSlashing(Container): + attestation_1: StableIndexedAttestation + attestation_2: StableIndexedAttestation +``` -*Note*: This container holds requests from the execution layer that are received in [ +#### `StableExecutionPayload` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableExecutionPayload(StableContainer[MAX_EXECUTION_PAYLOAD_FIELDS]): + parent_hash: Optional[Hash32] + fee_recipient: Optional[ExecutionAddress] # 'beneficiary' in the yellow paper + state_root: Optional[Bytes32] + receipts_root: Optional[Bytes32] + logs_bloom: Optional[ByteVector[BYTES_PER_LOGS_BLOOM]] + prev_randao: Optional[Bytes32] # 'difficulty' in the yellow paper + block_number: Optional[uint64] # 'number' in the yellow paper + gas_limit: Optional[uint64] + gas_used: Optional[uint64] + timestamp: Optional[uint64] + extra_data: Optional[ByteList[MAX_EXTRA_DATA_BYTES]] + base_fee_per_gas: Optional[uint256] + block_hash: Optional[Hash32] # Hash of execution block + transactions: Optional[List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]] + withdrawals: Optional[List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]] # [New in Capella] + blob_gas_used: Optional[uint64] # [New in Deneb:EIP4844] + excess_blob_gas: Optional[uint64] # [New in Deneb:EIP4844] +``` + +#### `StableExecutionPayloadHeader` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableExecutionPayloadHeader(StableContainer[MAX_EXECUTION_PAYLOAD_FIELDS]): + parent_hash: Optional[Hash32] + fee_recipient: Optional[ExecutionAddress] + state_root: Optional[Bytes32] + receipts_root: Optional[Bytes32] + logs_bloom: Optional[ByteVector[BYTES_PER_LOGS_BLOOM]] + prev_randao: Optional[Bytes32] + block_number: Optional[uint64] + gas_limit: Optional[uint64] + gas_used: Optional[uint64] + timestamp: Optional[uint64] + extra_data: Optional[ByteList[MAX_EXTRA_DATA_BYTES]] + base_fee_per_gas: Optional[uint256] + block_hash: Optional[Hash32] # Hash of execution block + transactions_root: Optional[Root] + withdrawals_root: Optional[Root] # [New in Capella] + blob_gas_used: Optional[uint64] # [New in Deneb:EIP4844] + excess_blob_gas: Optional[uint64] # [New in Deneb:EIP4844] +``` + +#### `StableExecutionRequests` + +*Note*: This `StableContainer` holds requests from the execution layer that are received in [ `ExecutionPayloadV4`](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#executionpayloadv4) via the Engine API. These requests are required for CL state transition (see `BeaconBlockBody`). ```python -class ExecutionRequests(Container): - deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110] - withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7002:EIP7251] - consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7251] +class StableExecutionRequests(StableContainer[MAX_EXECUTION_REQUESTS_FIELDS]): + deposits: Optional[List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD]] # [New in Electra:EIP6110] + withdrawals: Optional[List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]] # [New in Electra:EIP7002:EIP7251] + consolidations: Optional[List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD]] # [New in Electra:EIP7251] +``` + +#### `StableBeaconBlockBody` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableBeaconBlockBody(StableContainer[MAX_BEACON_BLOCK_BODY_FIELDS]): + randao_reveal: Optional[BLSSignature] + eth1_data: Optional[Eth1Data] # Eth1 data vote + graffiti: Optional[Bytes32] # Arbitrary data + proposer_slashings: Optional[List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]] + # [Modified in Electra:EIP7549] + attester_slashings: Optional[List[StableAttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA]] + attestations: Optional[List[StableAttestation, MAX_ATTESTATIONS_ELECTRA]] # [Modified in Electra:EIP7549] + deposits: Optional[List[Deposit, MAX_DEPOSITS]] + voluntary_exits: Optional[List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]] + sync_aggregate: Optional[SyncAggregate] # [New in Altair] + execution_payload: Optional[StableExecutionPayload] # [New in Bellatrix] + # [New in Capella] + bls_to_execution_changes: Optional[List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]] + blob_kzg_commitments: Optional[List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]] # [New in Deneb:EIP4844] + execution_requests: Optional[StableExecutionRequests] # [New in Electra] +``` + +#### `StableBeaconState` + +*Note*: The `StableContainer` is new in EIP7688. + +```python +class StableBeaconState(StableContainer[MAX_BEACON_STATE_FIELDS]): + # Versioning + genesis_time: Optional[uint64] + genesis_validators_root: Optional[Root] + slot: Optional[Slot] + fork: Optional[Fork] + # History + latest_block_header: Optional[BeaconBlockHeader] + block_roots: Optional[Vector[Root, SLOTS_PER_HISTORICAL_ROOT]] + state_roots: Optional[Vector[Root, SLOTS_PER_HISTORICAL_ROOT]] + # Frozen in Capella, replaced by historical_summaries + historical_roots: Optional[List[Root, HISTORICAL_ROOTS_LIMIT]] + # Eth1 + eth1_data: Optional[Eth1Data] + eth1_data_votes: Optional[List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]] + eth1_deposit_index: Optional[uint64] + # Registry + validators: Optional[List[Validator, VALIDATOR_REGISTRY_LIMIT]] + balances: Optional[List[Gwei, VALIDATOR_REGISTRY_LIMIT]] + # Randomness + randao_mixes: Optional[Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]] + # Slashings + slashings: Optional[Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR]] # Per-epoch sums of slashed effective balances + # Participation + previous_epoch_participation: Optional[List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]] # [Modified in Altair] + current_epoch_participation: Optional[List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]] # [Modified in Altair] + # Finality + justification_bits: Optional[Bitvector[JUSTIFICATION_BITS_LENGTH]] # Bit set for every recent justified epoch + previous_justified_checkpoint: Optional[Checkpoint] + current_justified_checkpoint: Optional[Checkpoint] + finalized_checkpoint: Optional[Checkpoint] + # Inactivity + inactivity_scores: Optional[List[uint64, VALIDATOR_REGISTRY_LIMIT]] # [New in Altair] + # Sync + current_sync_committee: Optional[SyncCommittee] # [New in Altair] + next_sync_committee: Optional[SyncCommittee] # [New in Altair] + # Execution + latest_execution_payload_header: Optional[StableExecutionPayloadHeader] # [New in Bellatrix] + # Withdrawals + next_withdrawal_index: Optional[WithdrawalIndex] # [New in Capella] + next_withdrawal_validator_index: Optional[ValidatorIndex] # [New in Capella] + # Deep history valid from Capella onwards + historical_summaries: Optional[List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]] # [New in Capella] + deposit_requests_start_index: Optional[uint64] # [New in Electra:EIP6110] + deposit_balance_to_consume: Optional[Gwei] # [New in Electra:EIP7251] + exit_balance_to_consume: Optional[Gwei] # [New in Electra:EIP7251] + earliest_exit_epoch: Optional[Epoch] # [New in Electra:EIP7251] + consolidation_balance_to_consume: Optional[Gwei] # [New in Electra:EIP7251] + earliest_consolidation_epoch: Optional[Epoch] # [New in Electra:EIP7251] + # [New in Electra:EIP7251] + pending_balance_deposits: Optional[List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT]] + # [New in Electra:EIP7251] + pending_partial_withdrawals: Optional[List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]] + # [New in Electra:EIP7251] + pending_consolidations: Optional[List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]] ``` ### Modified Containers @@ -280,10 +476,32 @@ class AttesterSlashing(Container): attestation_2: IndexedAttestation # [Modified in Electra:EIP7549] ``` +### `Profile` definitions + +#### `Attestation` + +```python +class Attestation(Profile[StableAttestation]): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in Electra:EIP7549] + data: AttestationData + signature: BLSSignature + committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in Electra:EIP7549] +``` + +#### `IndexedAttestation` + +```python +class IndexedAttestation(Profile[StableIndexedAttestation]): + # [Modified in Electra:EIP7549] + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + data: AttestationData + signature: BLSSignature +``` + #### `BeaconBlockBody` ```python -class BeaconBlockBody(Container): +class BeaconBlockBody(Profile[StableBeaconBlockBody]): randao_reveal: BLSSignature eth1_data: Eth1Data # Eth1 data vote graffiti: Bytes32 # Arbitrary data @@ -301,32 +519,69 @@ class BeaconBlockBody(Container): execution_requests: ExecutionRequests # [New in Electra] ``` -### Extended Containers +#### `ExecutionPayload` -#### `Attestation` +```python +class ExecutionPayload(Profile[StableExecutionPayload]): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + blob_gas_used: uint64 + excess_blob_gas: uint64 +``` + +#### `ExecutionPayloadHeader` ```python -class Attestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in Electra:EIP7549] - data: AttestationData - signature: BLSSignature - committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in Electra:EIP7549] +class ExecutionPayloadHeader(Profile[StableExecutionPayloadHeader]): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 + transactions_root: Root + withdrawals_root: Root + blob_gas_used: uint64 + excess_blob_gas: uint64 ``` -#### `IndexedAttestation` +#### `ExecutionRequests` ```python -class IndexedAttestation(Container): - # [Modified in Electra:EIP7549] - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - data: AttestationData - signature: BLSSignature +class ExecutionRequests(Profile[StableExecutionRequests]): + deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110] + withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7002:EIP7251] + consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7251] ``` #### `BeaconState` ```python -class BeaconState(Container): +class BeaconState(Profile[StableBeaconState]): # Versioning genesis_time: uint64 genesis_validators_root: Root @@ -945,7 +1200,7 @@ def notify_new_payload(self: ExecutionEngine, execution_requests: ExecutionRequests, parent_beacon_block_root: Root) -> bool: """ - Return ``True`` if and only if ``execution_payload`` and ``execution_requests`` + Return ``True`` if and only if ``execution_payload`` and ``execution_requests`` are valid with respect to ``self.execution_state``. """ ... @@ -974,8 +1229,8 @@ def verify_and_notify_new_payload(self: ExecutionEngine, # [Modified in Electra] if not self.notify_new_payload( - execution_payload, - execution_requests, + execution_payload, + execution_requests, parent_beacon_block_root): return False diff --git a/specs/electra/fork.md b/specs/electra/fork.md index 44c41ac0bf..72a4ef62d3 100644 --- a/specs/electra/fork.md +++ b/specs/electra/fork.md @@ -72,7 +72,25 @@ an irregular state change is made to upgrade to Electra. ```python def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: epoch = deneb.get_current_epoch(pre) - latest_execution_payload_header = pre.latest_execution_payload_header + latest_execution_payload_header = 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, + ) exit_epochs = [v.exit_epoch for v in pre.validators if v.exit_epoch != FAR_FUTURE_EPOCH] if not exit_epochs: diff --git a/specs/electra/light-client/fork.md b/specs/electra/light-client/fork.md index a315146b0e..8a07e58a11 100644 --- a/specs/electra/light-client/fork.md +++ b/specs/electra/light-client/fork.md @@ -39,8 +39,27 @@ A Electra `LightClientStore` can still process earlier light client data. In ord def upgrade_lc_header_to_electra(pre: deneb.LightClientHeader) -> LightClientHeader: return LightClientHeader( beacon=pre.beacon, - execution=pre.execution, - execution_branch=pre.execution_branch, + execution=ExecutionPayloadHeader( + parent_hash=pre.execution.parent_hash, + fee_recipient=pre.execution.fee_recipient, + state_root=pre.execution.state_root, + receipts_root=pre.execution.receipts_root, + logs_bloom=pre.execution.logs_bloom, + prev_randao=pre.execution.prev_randao, + block_number=pre.execution.block_number, + gas_limit=pre.execution.gas_limit, + gas_used=pre.execution.gas_used, + timestamp=pre.execution.timestamp, + extra_data=pre.execution.extra_data, + base_fee_per_gas=pre.execution.base_fee_per_gas, + block_hash=pre.execution.block_hash, + transactions_root=pre.execution.transactions_root, + withdrawals_root=pre.execution.withdrawals_root, + blob_gas_used=pre.execution.blob_gas_used, + excess_blob_gas=pre.execution.excess_blob_gas, + ), + execution_branch=normalize_merkle_branch( + pre.execution_branch, EXECUTION_PAYLOAD_GINDEX_ELECTRA), ) ``` diff --git a/specs/electra/light-client/full-node.md b/specs/electra/light-client/full-node.md index 0393aaec24..8c1d7424b8 100644 --- a/specs/electra/light-client/full-node.md +++ b/specs/electra/light-client/full-node.md @@ -51,7 +51,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: execution_header.excess_blob_gas = payload.excess_blob_gas execution_branch = ExecutionBranch( - compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)) + compute_merkle_proof(block.message.body, execution_payload_gindex_at_slot(block.message.slot))) else: # Note that during fork transitions, `finalized_header` may still point to earlier forks. # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), diff --git a/specs/electra/light-client/sync-protocol.md b/specs/electra/light-client/sync-protocol.md index 26da70ee34..136705c435 100644 --- a/specs/electra/light-client/sync-protocol.md +++ b/specs/electra/light-client/sync-protocol.md @@ -17,6 +17,7 @@ - [Modified `finalized_root_gindex_at_slot`](#modified-finalized_root_gindex_at_slot) - [Modified `current_sync_committee_gindex_at_slot`](#modified-current_sync_committee_gindex_at_slot) - [Modified `next_sync_committee_gindex_at_slot`](#modified-next_sync_committee_gindex_at_slot) + - [Modified `execution_payload_gindex_at_slot`](#modified-execution_payload_gindex_at_slot) - [Modified `get_lc_execution_root`](#modified-get_lc_execution_root) - [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header) @@ -38,26 +39,29 @@ Additional documents describes the impact of the upgrade on certain roles: | `FinalityBranch` | `Vector[Bytes32, floorlog2(FINALIZED_ROOT_GINDEX_ELECTRA)]` | Merkle branch of `finalized_checkpoint.root` within `BeaconState` | | `CurrentSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `current_sync_committee` within `BeaconState` | | `NextSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `next_sync_committee` within `BeaconState` | +| `ExecutionBranch` | `Vector[Bytes32, floorlog2(EXECUTION_PAYLOAD_GINDEX_ELECTRA)]` | Merkle branch of `execution_payload` within `BeaconBlockBody` | ## Constants ### Frozen constants -Existing `GeneralizedIndex` constants are frozen at their [Altair](../../altair/light-client/sync-protocol.md#constants) values. +Existing `GeneralizedIndex` constants are frozen at their [Altair](../../altair/light-client/sync-protocol.md#constants) and [Capella](../../capella/light-client/sync-protocol.md#constants) values. | Name | Value | | - | - | | `FINALIZED_ROOT_GINDEX` | `get_generalized_index(altair.BeaconState, 'finalized_checkpoint', 'root')` (= 105) | | `CURRENT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'current_sync_committee')` (= 54) | | `NEXT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'next_sync_committee')` (= 55) | +| `EXECUTION_PAYLOAD_GINDEX` | `get_generalized_index(capella.BeaconBlockBody, 'execution_payload')` (= 25) | ### New constants | Name | Value | | - | - | -| `FINALIZED_ROOT_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 169) | -| `CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'current_sync_committee')` (= 86) | -| `NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 87) | +| `FINALIZED_ROOT_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 553) | +| `CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'current_sync_committee')` (= 278) | +| `NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 279) | +| `EXECUTION_PAYLOAD_GINDEX_ELECTRA` | `get_generalized_index(BeaconBlockBody, 'execution_payload')` (= 137) | ## Helper functions @@ -97,6 +101,19 @@ def next_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: return NEXT_SYNC_COMMITTEE_GINDEX ``` +### Modified `execution_payload_gindex_at_slot` + +```python +def execution_payload_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + assert epoch >= CAPELLA_FORK_EPOCH + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return EXECUTION_PAYLOAD_GINDEX_ELECTRA + return EXECUTION_PAYLOAD_GINDEX +``` + ### Modified `get_lc_execution_root` ```python @@ -169,11 +186,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool: and header.execution_branch == ExecutionBranch() ) - return is_valid_merkle_branch( + return is_valid_normalized_merkle_branch( leaf=get_lc_execution_root(header), branch=header.execution_branch, - depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), - index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + gindex=execution_payload_gindex_at_slot(header.beacon.slot), root=header.beacon.body_root, ) ``` diff --git a/specs/electra/p2p-interface.md b/specs/electra/p2p-interface.md index 88d14813b3..a80ac3d863 100644 --- a/specs/electra/p2p-interface.md +++ b/specs/electra/p2p-interface.md @@ -11,6 +11,8 @@ The specification of these changes continues in the same format as the network s - [Modifications in Electra](#modifications-in-electra) + - [Preset](#preset) + - [Custom types](#custom-types) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) - [Global topics](#global-topics) @@ -23,6 +25,26 @@ The specification of these changes continues in the same format as the network s ## Modifications in Electra +### Preset + +Existing `PROOF_DEPTH` presets are frozen at their [Deneb](../../deneb/p2p-interface.md#preset) values. + +| Name | Value | +|------------------------------------------|-----------------------------------| +| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` | `uint64(floorlog2(get_generalized_index(deneb.BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK))` (= 17) | + +*[New in Electra:EIP7688]* + +| Name | Value | Description | +|------------------------------------------|-----------------------------------|---------------------------------------------------------------------| +| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA` | `uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK))` (= 20) | Merkle proof depth for `blob_kzg_commitments` list item | + +### Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `KZGCommitmentInclusionProof` | `Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA]` | Merkle branch of a single `blob_kzg_commitments` list item within `BeaconBlockBody` | + ### The gossip domain: gossipsub Some gossip meshes are upgraded in the fork of Electra to support upgraded types. diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py index 7f414ab285..cb69f3fdee 100644 --- a/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py @@ -6,6 +6,9 @@ from eth2spec.test.helpers.attestations import ( state_transition_with_full_block, ) +from eth2spec.test.helpers.light_client import ( + latest_execution_payload_gindex, +) @with_test_suite_name("BeaconBlockBody") @@ -15,7 +18,7 @@ def test_execution_merkle_proof(spec, state): block = state_transition_with_full_block(spec, state, True, False) yield "object", block.message.body - gindex = spec.EXECUTION_PAYLOAD_GINDEX + gindex = latest_execution_payload_gindex(spec) branch = spec.compute_merkle_proof(block.message.body, gindex) yield "proof", { "leaf": "0x" + block.message.body.execution_payload.hash_tree_root().hex(), diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py index 1f44257856..691ff9aa9f 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py @@ -5,6 +5,12 @@ ) +def latest_kzg_commitment_inclusion_proof_depth(spec): + if hasattr(spec, 'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA'): + return spec.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA + return spec.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH + + @with_deneb_and_later @spec_test @single_phase @@ -25,4 +31,4 @@ def test_networking(spec): assert spec.config.BLOB_SIDECAR_SUBNET_COUNT == spec.config.MAX_BLOBS_PER_BLOCK for i in range(spec.MAX_BLOB_COMMITMENTS_PER_BLOCK): gindex = spec.get_generalized_index(spec.BeaconBlockBody, 'blob_kzg_commitments', i) - assert spec.floorlog2(gindex) == spec.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH + assert spec.floorlog2(gindex) == latest_kzg_commitment_inclusion_proof_depth(spec) diff --git a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py index 886fc7cce0..4a95fe5f57 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py @@ -38,14 +38,12 @@ def run_fork_test(post_spec, pre_state): 'next_withdrawal_index', 'next_withdrawal_validator_index', # Deep history valid from Capella onwards 'historical_summaries', - 'latest_execution_payload_header' - ] for field in stable_fields: assert getattr(pre_state, field) == getattr(post_state, field) # Modified fields - modified_fields = ['fork'] + modified_fields = ['fork', 'latest_execution_payload_header'] for field in modified_fields: assert getattr(pre_state, field) != getattr(post_state, field) diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client.py b/tests/core/pyspec/eth2spec/test/helpers/light_client.py index 4638c988b5..296f5ee160 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/light_client.py +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client.py @@ -32,6 +32,12 @@ def latest_next_sync_committee_gindex(spec): return spec.NEXT_SYNC_COMMITTEE_GINDEX +def latest_execution_payload_gindex(spec): + if hasattr(spec, 'EXECUTION_PAYLOAD_GINDEX_ELECTRA'): + return spec.EXECUTION_PAYLOAD_GINDEX_ELECTRA + return spec.EXECUTION_PAYLOAD_GINDEX + + def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): return spec.compute_start_slot_at_epoch(sync_committee_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD)