Skip to content

Commit 38ab5c4

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 squashed commit) Squashed commits: [8b92f7b0f] 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 460d46d commit 38ab5c4

File tree

20 files changed

+330
-43
lines changed

20 files changed

+330
-43
lines changed

presets/mainnet/electra.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16
4343
# ---------------------------------------------------------------
4444
# 2**3 ( = 8) pending withdrawals
4545
MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
46+
47+
# Misc
48+
# ---------------------------------------------------------------
49+
# `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 12 = 20
50+
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 20

presets/minimal/electra.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2
4343
# ---------------------------------------------------------------
4444
# 2**0 ( = 1) pending withdrawals
4545
MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 1
46+
47+
# Misc
48+
# ---------------------------------------------------------------
49+
# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 7 + 1 + 4 = 12
50+
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_ELECTRA: 12

pysetup/helpers.py

+4
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[s
194194
for key, value in items:
195195
dependencies = []
196196
for line in value.split('\n'):
197+
profile_match = re.match(r'class\s+\w+\s*\(Profile\[\s*(\w+)\s*\]\s*\)\s*:', line)
198+
if profile_match is not None:
199+
dependencies.append(profile_match.group(1)) # SSZ `Profile` base
200+
continue
197201
if not re.match(r'\s+\w+: .+', line):
198202
continue # skip whitespace etc.
199203
line = line[line.index(':') + 1:] # strip of field name

pysetup/spec_builders/deneb.py

-5
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,5 @@ def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]:
7272
'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value,
7373
'MAX_BLOBS_PER_BLOCK': spec_object.config_vars['MAX_BLOBS_PER_BLOCK'].value,
7474
'MAX_BLOB_COMMITMENTS_PER_BLOCK': spec_object.preset_vars['MAX_BLOB_COMMITMENTS_PER_BLOCK'].value,
75-
}
76-
77-
@classmethod
78-
def hardcoded_func_dep_presets(cls, spec_object) -> Dict[str, str]:
79-
return {
8075
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH': spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH'].value,
8176
}

pysetup/spec_builders/electra.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ class ElectraSpecBuilder(BaseSpecBuilder):
1010
def imports(cls, preset_name: str):
1111
return f'''
1212
from eth2spec.deneb import {preset_name} as deneb
13+
from eth2spec.utils.ssz.ssz_typing import StableContainer, Profile
1314
'''
1415

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

setup.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def _update_constant_vars_with_kzg_setups(constant_vars, preset_name):
173173
constant_vars['KZG_SETUP_G1_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G1_MONOMIAL'].value, str(kzg_setups[0]), comment, None)
174174
constant_vars['KZG_SETUP_G1_LAGRANGE'] = VariableDefinition(constant_vars['KZG_SETUP_G1_LAGRANGE'].value, str(kzg_setups[1]), comment, None)
175175
constant_vars['KZG_SETUP_G2_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G2_MONOMIAL'].value, str(kzg_setups[2]), comment, None)
176-
176+
177177

178178
def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], preset_name=str) -> SpecObject:
179179
functions: Dict[str, str] = {}
@@ -227,7 +227,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr
227227
raise
228228

229229
if parent_class:
230-
assert parent_class == "Container"
230+
assert parent_class in ["Container", "StableContainer", "Profile"]
231231
# NOTE: trim whitespace from spec
232232
ssz_objects[current_name] = "\n".join(line.rstrip() for line in source.splitlines())
233233
else:
@@ -552,7 +552,7 @@ def run(self):
552552
"pycryptodome==3.15.0",
553553
"py_ecc==6.0.0",
554554
"milagro_bls_binding==1.9.0",
555-
"remerkleable==0.1.28",
555+
"remerkleable @ git+https://github.com/etan-status/remerkleable@dev/etan/sc-default",
556556
"trie==2.0.2",
557557
RUAMEL_YAML_VERSION,
558558
"lru-dict==1.2.0",

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

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

specs/capella/light-client/sync-protocol.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Containers](#containers)
1515
- [Modified `LightClientHeader`](#modified-lightclientheader)
1616
- [Helper functions](#helper-functions)
17+
- [`execution_payload_gindex_at_slot`](#execution_payload_gindex_at_slot)
1718
- [`get_lc_execution_root`](#get_lc_execution_root)
1819
- [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header)
1920

@@ -55,6 +56,16 @@ class LightClientHeader(Container):
5556

5657
## Helper functions
5758

59+
### `execution_payload_gindex_at_slot`
60+
61+
```python
62+
def execution_payload_gindex_at_slot(slot: Slot) -> GeneralizedIndex:
63+
epoch = compute_epoch_at_slot(slot)
64+
assert epoch >= CAPELLA_FORK_EPOCH
65+
66+
return EXECUTION_PAYLOAD_GINDEX
67+
```
68+
5869
### `get_lc_execution_root`
5970

6071
```python
@@ -79,11 +90,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool:
7990
and header.execution_branch == ExecutionBranch()
8091
)
8192

82-
return is_valid_merkle_branch(
93+
return is_valid_normalized_merkle_branch(
8394
leaf=get_lc_execution_root(header),
8495
branch=header.execution_branch,
85-
depth=floorlog2(EXECUTION_PAYLOAD_GINDEX),
86-
index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX),
96+
gindex=execution_payload_gindex_at_slot(header.beacon.slot),
8797
root=header.beacon.body_root,
8898
)
8999
```

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader:
5353
execution_header.excess_blob_gas = payload.excess_blob_gas
5454

5555
execution_branch = ExecutionBranch(
56-
compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX))
56+
compute_merkle_proof(block.message.body, execution_payload_gindex_at_slot(block.message.slot)))
5757
else:
5858
# Note that during fork transitions, `finalized_header` may still point to earlier forks.
5959
# While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`),

specs/deneb/light-client/sync-protocol.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool:
7777
and header.execution_branch == ExecutionBranch()
7878
)
7979

80-
return is_valid_merkle_branch(
80+
return is_valid_normalized_merkle_branch(
8181
leaf=get_lc_execution_root(header),
8282
branch=header.execution_branch,
83-
depth=floorlog2(EXECUTION_PAYLOAD_GINDEX),
84-
index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX),
83+
gindex=execution_payload_gindex_at_slot(header.beacon.slot),
8584
root=header.beacon.body_root,
8685
)
8786
```

specs/deneb/p2p-interface.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The specification of these changes continues in the same format as the network s
1414
- [Constant](#constant)
1515
- [Preset](#preset)
1616
- [Configuration](#configuration)
17+
- [Custom types](#custom-types)
1718
- [Containers](#containers)
1819
- [`BlobSidecar`](#blobsidecar)
1920
- [`BlobIdentifier`](#blobidentifier)
@@ -66,6 +67,12 @@ The specification of these changes continues in the same format as the network s
6667
| `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` | `2**12` (= 4096 epochs, ~18 days) | The minimum epoch range over which a node must serve blob sidecars |
6768
| `BLOB_SIDECAR_SUBNET_COUNT` | `6` | The number of blob sidecar subnets used in the gossipsub protocol. |
6869

70+
### Custom types
71+
72+
| Name | SSZ equivalent | Description |
73+
| - | - | - |
74+
| `KZGCommitmentInclusionProof` | `Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH]` | Merkle branch of a single `blob_kzg_commitments` list item within `BeaconBlockBody` |
75+
6976
### Containers
7077

7178
#### `BlobSidecar`
@@ -79,7 +86,7 @@ class BlobSidecar(Container):
7986
kzg_commitment: KZGCommitment
8087
kzg_proof: KZGProof # Allows for quick verification of kzg_commitment
8188
signed_block_header: SignedBeaconBlockHeader
82-
kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH]
89+
kzg_commitment_inclusion_proof: KZGCommitmentInclusionProof
8390
```
8491

8592
#### `BlobIdentifier`
@@ -98,12 +105,12 @@ class BlobIdentifier(Container):
98105

99106
```python
100107
def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool:
101-
gindex = get_subtree_index(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments', blob_sidecar.index))
108+
gindex = get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments', blob_sidecar.index)
102109
return is_valid_merkle_branch(
103110
leaf=blob_sidecar.kzg_commitment.hash_tree_root(),
104111
branch=blob_sidecar.kzg_commitment_inclusion_proof,
105-
depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH,
106-
index=gindex,
112+
depth=floorlog2(gindex),
113+
index=get_subtree_index(gindex),
107114
root=blob_sidecar.signed_block_header.message.body_root,
108115
)
109116
```

0 commit comments

Comments
 (0)