Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
41f2322
modify fork digest to distinguish BPO forks + add entry to ENR.
raulk Jun 2, 2025
b2e8dba
simplify compute_fork_digest to reuse get_max_blobs_per_block.
raulk Jun 3, 2025
803460b
Merge branch 'dev' into raulk/fulu-latest-bpo
jtraglia Jun 3, 2025
45b2ee1
Combine sections
jtraglia Jun 4, 2025
77d981e
Move get_max_blobs_per_block before compute_fork_digest
jtraglia Jun 4, 2025
0a9b17d
Fix test_compute_fork_digest
jtraglia Jun 4, 2025
0be632f
tests: fix README typo.
raulk Jun 6, 2025
738cca0
add tests for compute_fork_digest @ fulu.
raulk Jun 6, 2025
b00d6a1
address review comments on beacon-chain.md.
raulk Jun 9, 2025
fc0990b
address review comments on p2p-interface.md.
raulk Jun 9, 2025
4c841df
address review comments in tests.
raulk Jun 9, 2025
5d21628
add note about `nfd` pre-upgrade.
raulk Jun 9, 2025
35fd1df
Merge branch 'dev' into raulk/fulu-latest-bpo
raulk Jun 9, 2025
d2e7d5e
Add "fulu" to "new in" comments
jtraglia Jun 9, 2025
a3ec2b5
Join comments into paragraph
jtraglia Jun 9, 2025
a674c3e
rename parameters.
raulk Jun 9, 2025
ae4d000
Update fork digest tests
jtraglia Jun 10, 2025
a428187
Hash epoch & blob limit into digest
jtraglia Jun 10, 2025
2deef95
Merge remote-tracking branch 'upstream/dev' into raulk/fulu-latest-bpo
jtraglia Jun 10, 2025
82bf29b
Use existing spec functions
jtraglia Jun 10, 2025
0b6cd8e
Update comments
jtraglia Jun 10, 2025
c910bf1
Rename to fork epoch
jtraglia Jun 10, 2025
e5dc7a2
Rename version to fork_version
jtraglia Jun 10, 2025
353e407
Apply suggestions from in-person feedback
jtraglia Jun 10, 2025
a33876a
Update compute_fork_digest() usage in fulu p2p specs
jtraglia Jun 10, 2025
d2e1b50
Fix lint & feedback
jtraglia Jun 10, 2025
1e79dbc
Address review feedback
jtraglia Jun 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions specs/fulu/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [Introduction](#introduction)
- [Configuration](#configuration)
- [Blob schedule](#blob-schedule)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Block processing](#block-processing)
- [Execution payload](#execution-payload)
Expand All @@ -15,6 +16,8 @@
- [`BeaconState`](#beaconstate)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [New `get_max_blobs_per_block`](#new-get_max_blobs_per_block)
- [Modified `compute_fork_digest`](#modified-compute_fork_digest)
- [New `compute_proposer_indices`](#new-compute_proposer_indices)
- [Beacon state accessors](#beacon-state-accessors)
- [Modified `get_beacon_proposer_index`](#modified-get_beacon_proposer_index)
Expand All @@ -32,6 +35,18 @@ and is under active development.

## Configuration

### Blob schedule

*[New in EIP7892]* This schedule defines the maximum blobs per block limit for a
given epoch.

*Note*: The blob schedule is to be determined.

<!-- list-of-records:blob_schedule -->

| Epoch | Max Blobs Per Block | Description |
| ----- | ------------------- | ----------- |

## Beacon chain state transition function

### Block processing
Expand Down Expand Up @@ -151,6 +166,53 @@ class BeaconState(Container):

### Misc

#### New `get_max_blobs_per_block`

*[New in EIP7892]* This schedule defines the maximum blobs per block limit for a
given epoch.

```python
def get_max_blobs_per_block(epoch: Epoch) -> uint64:
"""
Return the maximum number of blobs that can be included in a block for a given epoch.
"""
for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True):
if epoch >= entry["EPOCH"]:
return entry["MAX_BLOBS_PER_BLOCK"]
return MAX_BLOBS_PER_BLOCK_ELECTRA
```

#### Modified `compute_fork_digest`

*Note:* The `compute_fork_digest` helper is updated to account for
Blob-Parameter-Only forks.

```python
def compute_fork_digest(
current_version: Version,
genesis_validators_root: Root,
current_epoch: Epoch, # [New in Fulu:EIP7892]
) -> ForkDigest:
"""
Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``,
with a XOR bitmask of the big-endian byte representation of current max_blobs_per_block.

This is a digest primarily used for domain separation on the p2p layer.
4-bytes suffices for practical separation of forks/chains.
"""
base_digest = compute_fork_data_root(current_version, genesis_validators_root)[:4]

# Find the blob parameters applicable to this epoch
max_blobs_per_block = get_max_blobs_per_block(current_epoch)

# Safely bitmask blob parameters into the digest
# Despite the downcasting from uint64 to Bytes4, max_blobs_per_block is bounded by the
# MAX_BLOB_COMMITMENTS_PER_BLOCK constant
mask = max_blobs_per_block.to_bytes(4, "big")
masked_digest = bytes(a ^ b for a, b in zip(base_digest, mask))
return ForkDigest(masked_digest)
```

#### New `compute_proposer_indices`

```python
Expand Down
27 changes: 0 additions & 27 deletions specs/fulu/das-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
- [Configuration](#configuration)
- [Data size](#data-size)
- [Custody setting](#custody-setting)
- [Blob schedule](#blob-schedule)
- [Containers](#containers)
- [`DataColumnSidecar`](#datacolumnsidecar)
- [`MatrixEntry`](#matrixentry)
- [Helper functions](#helper-functions)
- [`get_custody_groups`](#get_custody_groups)
- [`get_max_blobs_per_block`](#get_max_blobs_per_block)
- [`compute_columns_for_custody_group`](#compute_columns_for_custody_group)
- [`compute_matrix`](#compute_matrix)
- [`recover_matrix`](#recover_matrix)
Expand Down Expand Up @@ -70,18 +68,6 @@ specification.
| `NUMBER_OF_CUSTODY_GROUPS` | `128` | Number of custody groups available for nodes to custody |
| `CUSTODY_REQUIREMENT` | `4` | Minimum number of custody groups an honest node custodies and serves samples from |

### Blob schedule

*[New in EIP7892]* This schedule defines the maximum blobs per block limit for a
given epoch.

*Note*: The blob schedule is to be determined.

<!-- list-of-records:blob_schedule -->

| Epoch | Max Blobs Per Block | Description |
| ----- | ------------------- | ----------- |

### Containers

#### `DataColumnSidecar`
Expand Down Expand Up @@ -132,19 +118,6 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence
return sorted(custody_groups)
```

### `get_max_blobs_per_block`

```python
def get_max_blobs_per_block(epoch: Epoch) -> uint64:
"""
Return the maximum number of blobs that can be included in a block for a given epoch.
"""
for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True):
if epoch >= entry["EPOCH"]:
return entry["MAX_BLOBS_PER_BLOCK"]
return MAX_BLOBS_PER_BLOCK_ELECTRA
```

### `compute_columns_for_custody_group`

```python
Expand Down
20 changes: 20 additions & 0 deletions specs/fulu/p2p-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- [The discovery domain: discv5](#the-discovery-domain-discv5)
- [ENR structure](#enr-structure)
- [Custody group count](#custody-group-count)
- [Next fork digest](#next-fork-digest)

<!-- mdformat-toc end -->

Expand Down Expand Up @@ -494,3 +495,22 @@ column discovery.
| Key | Value |
| ----- | ----------------------------------------------------------------------------------------------------------------- |
| `cgc` | Custody group count, `uint64` big endian integer with no leading zero bytes (`0` is encoded as empty byte string) |

##### Next fork digest

A new entry is added to the ENR under the key `nfd`, short for _next fork
digest_. This entry communicates the digest of the next scheduled fork,
regardless of whether it is a regular or a Blob-Parameters-Only fork.

If no next fork is scheduled, the `nfd` entry contains the default value for the
type (i.e., the SSZ representation of a zero-filled array).

| Key | Value |
| :---- | :---------------------- |
| `nfd` | SSZ Bytes4 `ForkDigest` |

When discovering and interfacing with peers, nodes MUST evaluate `nfd` alongside
their existing consideration of the `ENRForkID::next_*` fields under the `eth2`
key, to form a more accurate view of the peer's intended next fork for the
purposes of sustained peering. A mismatch indicates that the node MUST
disconnect from such peers at the fork boundary, but not sooner.
2 changes: 1 addition & 1 deletion tests/core/pyspec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Or, to run a specific test function specify `k=<test-name>`:
make test k=test_verify_kzg_proof
```

Or, to run a specific test function under a single fork specify `k=<test-name>`:
Or, to run all tests under a single fork specify `fork=<name>`:

```shell
make test fork=phase0
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from eth2spec.test.context import (
spec_state_test,
with_config_overrides,
with_fulu_and_later,
)


@with_fulu_and_later
@spec_state_test
@with_config_overrides(
{
"MAX_BLOBS_PER_BLOCK_ELECTRA": 9,
"FULU_FORK_EPOCH": 5,
"BLOB_SCHEDULE": [
{"EPOCH": 6, "MAX_BLOBS_PER_BLOCK": 12},
{"EPOCH": 10, "MAX_BLOBS_PER_BLOCK": 15},
],
},
emit=False,
)
def test_compute_fork_digest(spec, state):
test_cases = [
(5, 9), # FULU_FORK_EPOCH, should use MAX_BLOBS_PER_BLOCK_ELECTRA
(6, 12), # First BLOB_SCHEDULE entry
(10, 15), # Second BLOB_SCHEDULE entry
(11, 15), # After last BLOB_SCHEDULE entry, should stay at 15
]

def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))

for epoch, expected_max_blobs in test_cases:
expected_fork_data_root = spec.hash_tree_root(
spec.ForkData(
current_version=state.fork.current_version,
genesis_validators_root=state.genesis_validators_root,
)
)
actual_fork_digest = spec.compute_fork_digest(
state.fork.current_version, state.genesis_validators_root, epoch
)
expected_max_blobs_bytes = expected_max_blobs.to_bytes(4, "big")
expected_fork_digest = spec.ForkDigest(
xor_bytes(expected_fork_data_root[:4], expected_max_blobs_bytes)
)
assert actual_fork_digest == expected_fork_digest
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
spec_state_test,
spec_test,
with_all_phases,
with_all_phases_from_to,
with_phases,
)
from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation
from eth2spec.test.helpers.block import build_empty_block
from eth2spec.test.helpers.constants import PHASE0
from eth2spec.test.helpers.constants import FULU, PHASE0
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.forks import is_post_fulu
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.state import next_epoch
from eth2spec.utils import bls
Expand Down Expand Up @@ -336,12 +338,19 @@ def test_get_block_signature(spec, state):
)


@with_all_phases
@with_all_phases_from_to(from_phase=PHASE0, to_phase=FULU, other_phases=[FULU])
@spec_state_test
def test_compute_fork_digest(spec, state):
actual_fork_digest = spec.compute_fork_digest(
state.fork.current_version, state.genesis_validators_root
)
if is_post_fulu(spec):
actual_fork_digest = spec.compute_fork_digest(
state.fork.current_version,
state.genesis_validators_root,
spec.compute_epoch_at_slot(state.slot),
)
else:
actual_fork_digest = spec.compute_fork_digest(
state.fork.current_version, state.genesis_validators_root
)

expected_fork_data_root = spec.hash_tree_root(
spec.ForkData(
Expand Down