Skip to content

Commit 2d85164

Browse files
committed
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.
1 parent 7f8435d commit 2d85164

File tree

24 files changed

+653
-68
lines changed

24 files changed

+653
-68
lines changed

presets/mainnet/electra.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
4848
# ---------------------------------------------------------------
4949
# 2**4 ( = 4) pending deposits
5050
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
51+
52+
# Misc
53+
# ---------------------------------------------------------------
54+
# `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 12 = 20
55+
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 20

presets/mainnet/fulu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ FIELD_ELEMENTS_PER_CELL: 64
77
# `uint64(2 * 4096)` (= 8192)
88
FIELD_ELEMENTS_PER_EXT_BLOB: 8192
99
# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
10-
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4
10+
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 7

presets/minimal/electra.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2
4848
# ---------------------------------------------------------------
4949
# 2**4 ( = 4) pending deposits
5050
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
51+
52+
# Misc
53+
# ---------------------------------------------------------------
54+
# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 5 = 13
55+
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 13

presets/minimal/fulu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ FIELD_ELEMENTS_PER_CELL: 64
77
# `uint64(2 * 4096)` (= 8192)
88
FIELD_ELEMENTS_PER_EXT_BLOB: 8192
99
# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
10-
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4
10+
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 7

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dependencies = [
2323
"py_arkworks_bls12381==0.3.8",
2424
"py_ecc==6.0.0",
2525
"pycryptodome==3.21.0",
26-
"remerkleable==0.1.28",
26+
"remerkleable @ git+https://github.com/etan-status/remerkleable@dev/etan/sc-default",
2727
"ruamel.yaml==0.17.21",
2828
"setuptools==75.8.0",
2929
"trie==3.0.1",

pysetup/spec_builders/deneb.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ def verify_and_notify_new_payload(self: ExecutionEngine,
8181

8282

8383
@classmethod
84-
def hardcoded_func_dep_presets(cls, spec_object) -> Dict[str, str]:
84+
def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]:
8585
return {
86+
'BYTES_PER_FIELD_ELEMENT': spec_object.constant_vars['BYTES_PER_FIELD_ELEMENT'].value,
87+
'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value,
88+
'MAX_BLOBS_PER_BLOCK': spec_object.config_vars['MAX_BLOBS_PER_BLOCK'].value,
89+
'MAX_BLOB_COMMITMENTS_PER_BLOCK': spec_object.preset_vars['MAX_BLOB_COMMITMENTS_PER_BLOCK'].value,
8690
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH': spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH'].value,
8791
}

pysetup/spec_builders/eip7732.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ def concat_generalized_indices(*indices: GeneralizedIndex) -> GeneralizedIndex:
2626
@classmethod
2727
def deprecate_constants(cls) -> Set[str]:
2828
return set([
29-
'EXECUTION_PAYLOAD_GINDEX',
29+
'EXECUTION_PAYLOAD_GINDEX_ELECTRA',
3030
])
3131

3232
@classmethod
3333
def deprecate_presets(cls) -> Set[str]:
3434
return set([
35-
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH',
35+
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA',
3636
])

pysetup/spec_builders/electra.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,22 @@ def imports(cls, preset_name: str):
1111
return f'''
1212
from eth2spec.deneb import {preset_name} as deneb
1313
from eth2spec.utils.ssz.ssz_impl import ssz_serialize, ssz_deserialize
14+
from eth2spec.utils.ssz.ssz_typing import StableContainer, Profile
1415
'''
1516

1617
@classmethod
1718
def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]:
1819
return {
19-
'FINALIZED_ROOT_GINDEX_ELECTRA': 'GeneralizedIndex(169)',
20-
'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(86)',
21-
'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(87)',
20+
'FINALIZED_ROOT_GINDEX_ELECTRA': 'GeneralizedIndex(553)',
21+
'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(278)',
22+
'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(279)',
23+
'EXECUTION_PAYLOAD_GINDEX_ELECTRA': 'GeneralizedIndex(137)',
24+
}
25+
26+
@classmethod
27+
def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]:
28+
return {
29+
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA': spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA'].value,
2230
}
2331

2432

@@ -58,4 +66,4 @@ def verify_and_notify_new_payload(self: ExecutionEngine,
5866
return True
5967
6068
61-
EXECUTION_ENGINE = NoopExecutionEngine()"""
69+
EXECUTION_ENGINE = NoopExecutionEngine()"""

specs/_features/eip7732/beacon-chain.md

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
- [`ExecutionPayloadEnvelope`](#executionpayloadenvelope)
2626
- [`SignedExecutionPayloadEnvelope`](#signedexecutionpayloadenvelope)
2727
- [Modified containers](#modified-containers)
28+
- [`StableBeaconBlockBody`](#stablebeaconblockbody)
2829
- [`BeaconBlockBody`](#beaconblockbody)
30+
- [`StableExecutionPayloadHeader`](#stableexecutionpayloadheader)
2931
- [`ExecutionPayloadHeader`](#executionpayloadheader)
32+
- [`StableBeaconState`](#stablebeaconstate)
3033
- [`BeaconState`](#beaconstate)
3134
- [Helper functions](#helper-functions)
3235
- [Math](#math)
@@ -178,12 +181,34 @@ class SignedExecutionPayloadEnvelope(Container):
178181

179182
### Modified containers
180183

181-
#### `BeaconBlockBody`
184+
#### `StableBeaconBlockBody`
182185

183186
**Note:** The Beacon Block body is modified to contain a `Signed ExecutionPayloadHeader`. The containers `BeaconBlock` and `SignedBeaconBlock` are modified indirectly. The field `execution_requests` is removed from the beacon block body and moved into the signed execution payload envelope.
184187

185188
```python
186-
class BeaconBlockBody(Container):
189+
class StableBeaconBlockBody(StableContainer[MAX_BEACON_BLOCK_BODY_FIELDS]):
190+
randao_reveal: Optional[BLSSignature]
191+
eth1_data: Optional[Eth1Data] # Eth1 data vote
192+
graffiti: Optional[Bytes32] # Arbitrary data
193+
proposer_slashings: Optional[List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]]
194+
attester_slashings: Optional[List[StableAttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA]]
195+
attestations: Optional[List[Attestation, MAX_ATTESTATIONS_ELECTRA]]
196+
deposits: Optional[List[Deposit, MAX_DEPOSITS]]
197+
voluntary_exits: Optional[List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]]
198+
sync_aggregate: Optional[SyncAggregate]
199+
execution_payload: Optional[StableExecutionPayload] # [Removed in EIP-7732]
200+
bls_to_execution_changes: Optional[List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]]
201+
blob_kzg_commitments: Optional[List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]] # [Removed in EIP-7732]
202+
execution_requests: Optional[StableExecutionRequests] # [Removed in EIP-7732]
203+
# PBS
204+
signed_execution_payload_header: Optional[SignedExecutionPayloadHeader] # [New in EIP-7732]
205+
payload_attestations: Optional[List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS]] # [New in EIP-7732]
206+
```
207+
208+
#### `BeaconBlockBody`
209+
210+
```python
211+
class BeaconBlockBody(Profile[StableBeaconBlockBody]):
187212
randao_reveal: BLSSignature
188213
eth1_data: Eth1Data # Eth1 data vote
189214
graffiti: Bytes32 # Arbitrary data
@@ -204,12 +229,26 @@ class BeaconBlockBody(Container):
204229
payload_attestations: List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS] # [New in EIP-7732]
205230
```
206231

232+
#### `StableExecutionPayloadHeader`
233+
234+
```python
235+
class StableExecutionPayloadHeader(StableContainer[MAX_EXECUTION_PAYLOAD_FIELDS]):
236+
parent_block_hash: Optional[Hash32]
237+
parent_block_root: Optional[Root]
238+
block_hash: Optional[Hash32]
239+
gas_limit: Optional[uint64]
240+
builder_index: Optional[ValidatorIndex]
241+
slot: Optional[Slot]
242+
value: Optional[Gwei]
243+
blob_kzg_commitments_root: Optional[Root]
244+
```
245+
207246
#### `ExecutionPayloadHeader`
208247

209248
**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information, gas limit and KZG commitments root to verify the inclusion proofs.
210249

211250
```python
212-
class ExecutionPayloadHeader(Container):
251+
class ExecutionPayloadHeader(Profile[StableExecutionPayloadHeader]):
213252
parent_block_hash: Hash32
214253
parent_block_root: Root
215254
block_hash: Hash32
@@ -220,12 +259,73 @@ class ExecutionPayloadHeader(Container):
220259
blob_kzg_commitments_root: Root
221260
```
222261

262+
#### `StableBeaconState`
263+
264+
```python
265+
class StableBeaconState(StableContainer[MAX_BEACON_STATE_FIELDS]):
266+
# Versioning
267+
genesis_time: Optional[uint64]
268+
genesis_validators_root: Optional[Root]
269+
slot: Optional[Slot]
270+
fork: Optional[Fork]
271+
# History
272+
latest_block_header: Optional[BeaconBlockHeader]
273+
block_roots: Optional[Vector[Root, SLOTS_PER_HISTORICAL_ROOT]]
274+
state_roots: Optional[Vector[Root, SLOTS_PER_HISTORICAL_ROOT]]
275+
# Frozen in Capella, replaced by historical_summaries
276+
historical_roots: Optional[List[Root, HISTORICAL_ROOTS_LIMIT]]
277+
# Eth1
278+
eth1_data: Optional[Eth1Data]
279+
eth1_data_votes: Optional[List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]]
280+
eth1_deposit_index: Optional[uint64]
281+
# Registry
282+
validators: Optional[List[Validator, VALIDATOR_REGISTRY_LIMIT]]
283+
balances: Optional[List[Gwei, VALIDATOR_REGISTRY_LIMIT]]
284+
# Randomness
285+
randao_mixes: Optional[Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]]
286+
# Slashings
287+
slashings: Optional[Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR]] # Per-epoch sums of slashed effective balances
288+
# Participation
289+
previous_epoch_participation: Optional[List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]]
290+
current_epoch_participation: Optional[List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]]
291+
# Finality
292+
justification_bits: Optional[Bitvector[JUSTIFICATION_BITS_LENGTH]] # Bit set for every recent justified epoch
293+
previous_justified_checkpoint: Optional[Checkpoint]
294+
current_justified_checkpoint: Optional[Checkpoint]
295+
finalized_checkpoint: Optional[Checkpoint]
296+
# Inactivity
297+
inactivity_scores: Optional[List[uint64, VALIDATOR_REGISTRY_LIMIT]]
298+
# Sync
299+
current_sync_committee: Optional[SyncCommittee]
300+
next_sync_committee: Optional[SyncCommittee]
301+
# Execution
302+
latest_execution_payload_header: Optional[StableExecutionPayloadHeader]
303+
# Withdrawals
304+
next_withdrawal_index: Optional[WithdrawalIndex]
305+
next_withdrawal_validator_index: Optional[ValidatorIndex]
306+
# Deep history valid from Capella onwards
307+
historical_summaries: Optional[List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]]
308+
deposit_requests_start_index: Optional[uint64]
309+
deposit_balance_to_consume: Optional[Gwei]
310+
exit_balance_to_consume: Optional[Gwei]
311+
earliest_exit_epoch: Optional[Epoch]
312+
consolidation_balance_to_consume: Optional[Gwei]
313+
earliest_consolidation_epoch: Optional[Epoch]
314+
pending_deposits: Optional[List[PendingDeposit, PENDING_DEPOSITS_LIMIT]]
315+
pending_partial_withdrawals: Optional[List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]]
316+
pending_consolidations: Optional[List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]]
317+
# PBS
318+
latest_block_hash: Optional[Hash32] # [New in EIP-7732]
319+
latest_full_slot: Optional[Slot] # [New in EIP-7732]
320+
latest_withdrawals_root: Optional[Root] # [New in EIP-7732]
321+
```
322+
223323
#### `BeaconState`
224324

225325
*Note*: The `BeaconState` is modified to track the last withdrawals honored in the CL. The `latest_execution_payload_header` is modified semantically to refer not to a past committed `ExecutionPayload` but instead it corresponds to the state's slot builder's bid. Another addition is to track the last committed block hash and the last slot that was full, that is in which there were both consensus and execution blocks included.
226326

227327
```python
228-
class BeaconState(Container):
328+
class BeaconState(Profile[StableBeaconState]):
229329
# Versioning
230330
genesis_time: uint64
231331
genesis_validators_root: Root

specs/capella/light-client/full-node.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader:
4545
withdrawals_root=hash_tree_root(payload.withdrawals),
4646
)
4747
execution_branch = ExecutionBranch(
48-
compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX))
48+
compute_merkle_proof(block.message.body, execution_payload_gindex_at_slot(block.message.slot)))
4949
else:
5050
# Note that during fork transitions, `finalized_header` may still point to earlier forks.
5151
# While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`),

0 commit comments

Comments
 (0)