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/helpers.py b/pysetup/helpers.py index 589ae6ab58..ab9855f098 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -194,6 +194,10 @@ def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[s for key, value in items: dependencies = [] for line in value.split('\n'): + profile_match = re.match(r'class\s+\w+\s*\(Profile\[\s*(\w+)\s*\]\s*\)\s*:', line) + if profile_match is not None: + dependencies.append(profile_match.group(1)) # SSZ `Profile` base + continue if not re.match(r'\s+\w+: .+', line): continue # skip whitespace etc. line = line[line.index(':') + 1:] # strip of field name 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 ca02ee927c..cd8e9c9d40 100644 --- a/pysetup/spec_builders/electra.py +++ b/pysetup/spec_builders/electra.py @@ -10,12 +10,20 @@ 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 539db215b7..4011fb4d62 100644 --- a/setup.py +++ b/setup.py @@ -173,7 +173,7 @@ def _update_constant_vars_with_kzg_setups(constant_vars, preset_name): constant_vars['KZG_SETUP_G1_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G1_MONOMIAL'].value, str(kzg_setups[0]), comment, None) constant_vars['KZG_SETUP_G1_LAGRANGE'] = VariableDefinition(constant_vars['KZG_SETUP_G1_LAGRANGE'].value, str(kzg_setups[1]), comment, None) constant_vars['KZG_SETUP_G2_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G2_MONOMIAL'].value, str(kzg_setups[2]), comment, None) - + def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], preset_name=str) -> SpecObject: functions: Dict[str, str] = {} @@ -227,7 +227,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr raise if parent_class: - assert parent_class == "Container" + assert parent_class in ["Container", "StableContainer", "Profile"] # NOTE: trim whitespace from spec ssz_objects[current_name] = "\n".join(line.rstrip() for line in source.splitlines()) else: @@ -552,7 +552,7 @@ def run(self): "pycryptodome==3.15.0", "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 d9185b2030..eb3b2699a2 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) - [Domains](#domains) @@ -30,9 +31,17 @@ - [`WithdrawalRequest`](#withdrawalrequest) - [`ConsolidationRequest`](#consolidationrequest) - [`PendingConsolidation`](#pendingconsolidation) + - [`StableContainer` definitions](#stablecontainer-definitions) + - [`StableAttestation`](#stableattestation) + - [`StableIndexedAttestation`](#stableindexedattestation) + - [`StableAttesterSlashing`](#stableattesterslashing) + - [`StableExecutionPayload`](#stableexecutionpayload) + - [`StableExecutionPayloadHeader`](#stableexecutionpayloadheader) + - [`StableBeaconBlockBody`](#stablebeaconblockbody) + - [`StableBeaconState`](#stablebeaconstate) - [Modified Containers](#modified-containers) - [`AttesterSlashing`](#attesterslashing) - - [Extended Containers](#extended-containers) + - [`Profile` definitions](#profile-definitions) - [`Attestation`](#attestation) - [`IndexedAttestation`](#indexedattestation) - [`BeaconBlockBody`](#beaconblockbody) @@ -116,6 +125,16 @@ 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_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 | @@ -226,6 +245,7 @@ class PendingPartialWithdrawal(Container): amount: Gwei withdrawable_epoch: Epoch ``` + #### `WithdrawalRequest` *Note*: The container is new in EIP7251:EIP7002. @@ -258,6 +278,185 @@ class PendingConsolidation(Container): target_index: ValidatorIndex ``` +### `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 +``` + +#### `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] + deposit_requests: Optional[List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD]] # [New in Electra:EIP6110] + # [New in Electra:EIP7002:EIP7251] + withdrawal_requests: Optional[List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]] + # [New in Electra:EIP7251] + consolidation_requests: Optional[List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD]] +``` + +#### `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] + deposit_requests_root: Optional[Root] # [New in Electra:EIP6110] + withdrawal_requests_root: Optional[Root] # [New in Electra:EIP7002:EIP7251] + consolidation_requests_root: Optional[Root] # [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] +``` + +#### `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 #### `AttesterSlashing` @@ -268,12 +467,12 @@ class AttesterSlashing(Container): attestation_2: IndexedAttestation # [Modified in Electra:EIP7549] ``` -### Extended Containers +### `Profile` definitions #### `Attestation` ```python -class Attestation(Container): +class Attestation(Profile[StableAttestation]): aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in Electra:EIP7549] data: AttestationData signature: BLSSignature @@ -283,7 +482,7 @@ class Attestation(Container): #### `IndexedAttestation` ```python -class IndexedAttestation(Container): +class IndexedAttestation(Profile[StableIndexedAttestation]): # [Modified in Electra:EIP7549] attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] data: AttestationData @@ -293,7 +492,7 @@ class IndexedAttestation(Container): #### `BeaconBlockBody` ```python -class BeaconBlockBody(Container): +class BeaconBlockBody(Profile[StableBeaconBlockBody]): randao_reveal: BLSSignature eth1_data: Eth1Data # Eth1 data vote graffiti: Bytes32 # Arbitrary data @@ -313,7 +512,7 @@ class BeaconBlockBody(Container): #### `ExecutionPayload` ```python -class ExecutionPayload(Container): +class ExecutionPayload(Profile[StableExecutionPayload]): # Execution block header fields parent_hash: Hash32 fee_recipient: ExecutionAddress @@ -343,7 +542,7 @@ class ExecutionPayload(Container): #### `ExecutionPayloadHeader` ```python -class ExecutionPayloadHeader(Container): +class ExecutionPayloadHeader(Profile[StableExecutionPayloadHeader]): # Execution block header fields parent_hash: Hash32 fee_recipient: ExecutionAddress @@ -371,7 +570,7 @@ class ExecutionPayloadHeader(Container): #### `BeaconState` ```python -class BeaconState(Container): +class BeaconState(Profile[StableBeaconState]): # Versioning genesis_time: uint64 genesis_validators_root: Root @@ -874,7 +1073,7 @@ def process_pending_balance_deposits(state: BeaconState) -> None: if processed_amount + deposit.amount > available_for_processing: break # Deposit fits in the churn, process it. Increase balance and consume churn. - else: + else: increase_balance(state, deposit.index, deposit.amount) processed_amount += deposit.amount # Regardless of how the deposit was handled, we move on in the queue. diff --git a/specs/electra/light-client/fork.md b/specs/electra/light-client/fork.md index d613df56a9..ce3014abdd 100644 --- a/specs/electra/light-client/fork.md +++ b/specs/electra/light-client/fork.md @@ -61,7 +61,8 @@ def upgrade_lc_header_to_electra(pre: deneb.LightClientHeader) -> LightClientHea withdrawal_requests_root=Root(), # [New in Electra:EIP7002:EIP7251] consolidation_requests_root=Root(), # [New in Electra:EIP7251] ), - execution_branch=pre.execution_branch, + 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 f08a2cc5ed..36c1c54831 100644 --- a/specs/electra/light-client/full-node.md +++ b/specs/electra/light-client/full-node.md @@ -57,7 +57,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: execution_header.consolidation_requests_root = hash_tree_root(payload.consolidation_requests) 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 ef9dcd5987..292075bc86 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 @@ -178,11 +195,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 ebdcaaa831..ebd0e59f60 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) @@ -22,6 +24,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/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) diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py index 1f3db2fe00..e3710e9353 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -2,6 +2,7 @@ # Ignore linter: This module makes importing SSZ types easy, and hides away the underlying library from the spec. from remerkleable.complex import Container, Vector, List +from remerkleable.stable_container import StableContainer, Profile from remerkleable.union import Union from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256 from remerkleable.bitfields import Bitvector, Bitlist