From 4d2f29bca49d6292ff6a4724f9de83d35cb9f774 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 16 Apr 2025 17:37:22 -0500 Subject: [PATCH 01/25] Add parsing for blob schedule table --- pyproject.toml | 1 + pysetup/helpers.py | 27 +++++++++++++++++++++---- pysetup/spec_builders/fulu.py | 1 + setup.py | 38 ++++++++++++++++++++++++++++++++++- specs/fulu/das-core.md | 15 ++++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c3ec2f5265..008fa3fd7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "curdleproofs==0.1.2", "eth-typing==5.2.1", "eth-utils==5.3.0", + "frozendict==2.4.6", "lru-dict==1.3.0", "marko==2.1.3", "milagro_bls_binding==1.9.0", diff --git a/pysetup/helpers.py b/pysetup/helpers.py index ed278aa5a1..c00d217b9a 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -87,8 +87,21 @@ def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str for name in spec_object.config_vars.keys(): functions_spec = re.sub(r"\b%s\b" % name, 'config.' + name, functions_spec) - def format_config_var(name: str, vardef: VariableDefinition) -> str: - if vardef.type_name is None: + def format_config_var(name: str, vardef) -> str: + if isinstance(vardef, list): + # A special case for list of records. + # TODO(jtraglia): make this better. + indent = " " * 4 + lines = [f"{name}=("] + for d in vardef: + line = indent*2 + "frozendict({\n" + for k, v in d.items(): + line += indent * 3 + f'"{k}": {v},\n' + line += indent*2 + "})," + lines.append(line) + lines.append(indent + "),") + return "\n".join(lines) + elif vardef.type_name is None: out = f'{name}={vardef.value},' else: out = f'{name}={vardef.type_name}({vardef.value}),' @@ -96,10 +109,16 @@ def format_config_var(name: str, vardef: VariableDefinition) -> str: out += f' # {vardef.comment}' return out + def format_config_var_param(value): + if isinstance(value, list): + # A special case for list of records. + return "tuple[frozendict[str, Any], ...]" + elif isinstance(value, VariableDefinition): + return value.type_name if value.type_name is not None else "int" + config_spec = 'class Configuration(NamedTuple):\n' config_spec += ' PRESET_BASE: str\n' - config_spec += '\n'.join(f' {k}: {v.type_name if v.type_name is not None else "int"}' - for k, v in spec_object.config_vars.items()) + config_spec += '\n'.join(f' {k}: {format_config_var_param(v)}' for k, v in spec_object.config_vars.items()) config_spec += '\n\n\nconfig = Configuration(\n' config_spec += f' PRESET_BASE="{preset_name}",\n' config_spec += '\n'.join(' ' + format_config_var(k, v) for k, v in spec_object.config_vars.items()) diff --git a/pysetup/spec_builders/fulu.py b/pysetup/spec_builders/fulu.py index 39befcb41e..f5cf0af329 100644 --- a/pysetup/spec_builders/fulu.py +++ b/pysetup/spec_builders/fulu.py @@ -10,6 +10,7 @@ class FuluSpecBuilder(BaseSpecBuilder): @classmethod def imports(cls, preset_name: str): return f''' +from frozendict import frozendict from eth2spec.electra import {preset_name} as electra ''' diff --git a/setup.py b/setup.py index bd7ad6a18c..fd2bb2144a 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import json import logging import os +import re import string import sys import warnings @@ -215,6 +216,8 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr current_name = None should_skip = False + list_of_records = None + list_of_records_name = None for child in document.children: if isinstance(child, BlankLine): continue @@ -256,7 +259,27 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr else: raise Exception("unrecognized python code element: " + source) elif isinstance(child, Table): - for row in child.children: + list_of_records_header = None + for i, row in enumerate(child.children): + # This will start as an empty list when there is a comment, + # which indicates that the next table is a list-of-records. After we're done parsing + # the table, we will reset this to None. + if list_of_records is not None: + if i == 0: + # Save the table header, this will be used for field names. + # Skip the last item, which is the description. + list_of_records_header = [ + # Convert the title to SNAKE_CASE + re.sub(r'\s+', '_', value.children[0].children.upper()) + for value in row.children[:-1] + ] + continue + list_of_records.append({ + list_of_records_header[i]: value.children[0].children + for i, value in enumerate(row.children[:-1]) + }) + continue + cells = row.children if len(cells) >= 2: name_cell = cells[0] @@ -311,10 +334,23 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr preset_dep_constant_vars[name] = value_def else: constant_vars[name] = value_def + # After processing the list of records table, set this to None so + # that the next table is processed appropriately + if list_of_records is not None: + config_vars[list_of_records_name] = list_of_records + list_of_records = None elif isinstance(child, HTMLBlock): if child.body.strip() == "": should_skip = True + # Handle list-of-records tables + match = re.match(r"", child.body.strip()) + if match: + # Initialize list-of-records, in the next iteration this will indicate that the + # table is a list-of-records and must be parsed differently. + list_of_records = [] + # Use regex to extract the desired configuration list name + list_of_records_name = match.group(1).upper() # Load KZG trusted setup from files if any('KZG_SETUP' in name for name in constant_vars): diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 9b88180d02..ec94c9191f 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -10,6 +10,7 @@ - [Configuration](#configuration) - [Data size](#data-size) - [Custody setting](#custody-setting) + - [Blob schedule](#blob-schedule) - [Containers](#containers) - [`DataColumnSidecar`](#datacolumnsidecar) - [`MatrixEntry`](#matrixentry) @@ -67,6 +68,20 @@ The following values are (non-configurable) constants used throughout the specif | `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 EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. + + + +| Epoch | Max Blobs Per Block | Description | +| -------------------------------------- | ------------------- | ------------------------------------------------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | Starting at epoch `269568`, the limit is `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | Starting at epoch `364032`, the limit is `9` blobs | +| `Epoch(18446744073709551615)` **BPO1** | `uint64(18)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `18` blobs | +| `Epoch(18446744073709551615)` **BPO2** | `uint64(36)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `36` blobs | +| `Epoch(18446744073709551615)` **BPO3** | `uint64(72)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `72` blobs | + ### Containers #### `DataColumnSidecar` From 43b3a291b196940c192925c319105f22dafc6d3d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 15:50:38 +0200 Subject: [PATCH 02/25] add parser for config in yaml --- configs/mainnet.yaml | 7 +++++++ configs/minimal.yaml | 7 +++++++ pysetup/helpers.py | 17 ++++++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 6afcbb2bef..cbc3b204da 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -186,3 +186,10 @@ MAX_REQUEST_PAYLOADS: 128 ATTESTATION_DEADLINE: 4 PROPOSER_INCLUSION_LIST_CUT_OFF: 11 VIEW_FREEZE_DEADLINE: 9 + +# BLOBS BPO +BLOB_SCHEDULE: + - EPOCH: 348618 + MAX_BLOBS_PER_BLOCK: 48 + - EPOCH: 355368 + MAX_BLOBS_PER_BLOCK: 56 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 5cfe9bd0e2..eba54f9353 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -183,3 +183,10 @@ MAX_REQUEST_PAYLOADS: 128 ATTESTATION_DEADLINE: 2 PROPOSER_INCLUSION_LIST_CUT_OFF: 5 VIEW_FREEZE_DEADLINE: 3 + +# BLOBS BPO +BLOB_SCHEDULE: + - EPOCH: 348618 + MAX_BLOBS_PER_BLOCK: 48 + - EPOCH: 355368 + MAX_BLOBS_PER_BLOCK: 56 diff --git a/pysetup/helpers.py b/pysetup/helpers.py index c00d217b9a..dadc3e493e 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -1,5 +1,5 @@ import re -from typing import TypeVar, Dict +from typing import TypeVar, Dict, Union import textwrap from functools import reduce @@ -285,16 +285,23 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: ) -def parse_config_vars(conf: Dict[str, str]) -> Dict[str, str]: +def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, Dict[str, str]]]: """ Parses a dict of basic str/int/list types into a dict for insertion into the spec code. """ - out: Dict[str, str] = dict() + out: Dict[str, Union[str, Dict[str, str]]] = dict() for k, v in conf.items(): if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE' or k == 'CONFIG_NAME'): # Represent byte data with string, to avoid misinterpretation as big-endian int. # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. out[k] = f"'{v}'" else: - out[k] = str(int(v)) - return out + if k == 'BLOB_SCHEDULE': + blob_schedule = {} + for BPO in v: + blob_schedule[BPO['EPOCH']] = {"MAX_BLOBS_PER_BLOCK": BPO['MAX_BLOBS_PER_BLOCK']} + out[k] = blob_schedule + + else: + out[k] = str(int(v)) + return out \ No newline at end of file From e736f28b9e1af918f21715d40852b17e35516726 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 15:55:13 +0200 Subject: [PATCH 03/25] improve regex --- pysetup/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysetup/helpers.py b/pysetup/helpers.py index dadc3e493e..e1efae0036 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -85,7 +85,7 @@ def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str # Access global dict of config vars for runtime configurables for name in spec_object.config_vars.keys(): - functions_spec = re.sub(r"\b%s\b" % name, 'config.' + name, functions_spec) + functions_spec = re.sub(r"(? str: if isinstance(vardef, list): From c790cec016850592d4f7fa7423c0d55c0f4b321d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 16:26:01 +0200 Subject: [PATCH 04/25] add get_max_blobs_per_block --- specs/fulu/das-core.md | 10 ++++++++++ .../fulu/unittests/test_config_invariants.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index ec94c9191f..981653d7de 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -16,6 +16,7 @@ - [`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) @@ -133,6 +134,15 @@ 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) -> int: + for entry in reversed(sorted(BLOB_SCHEDULE, key=lambda e: e['EPOCH'])): + if entry["EPOCH"] < epoch: + return entry["MAX_BLOBS_PER_BLOCK"] +``` + ### `compute_columns_for_custody_group` ```python diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index c765f4ff25..ec54a88145 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -4,6 +4,13 @@ with_fulu_and_later, ) +from eth2spec.test.helpers.constants import ( + MAINNET, + MINIMAL, +) +from eth2spec.test.context import with_presets +from eth2spec.test.context import spec_state_test, with_phases, FULU + @with_fulu_and_later @spec_test @@ -32,3 +39,12 @@ def test_polynomial_commitments_sampling(spec): @single_phase def test_networking(spec): assert spec.config.MAX_BLOBS_PER_BLOCK_FULU <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + + +@with_fulu_and_later +@spec_state_test +def test_get_max_blobs(spec, state): + max_blobs = spec.get_max_blobs_per_block(269568 + 1) + assert max_blobs == 6 + max_blobs = spec.get_max_blobs_per_block(364032 + 1) + assert max_blobs == 9 From fcda062dc1dde5ec96940ddaa28d892d87ca7f31 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 16:32:56 +0200 Subject: [PATCH 05/25] remove MAX_BLOBS_PER_BLOCK_FULU --- configs/mainnet.yaml | 15 ++++++++++----- configs/minimal.yaml | 15 ++++++++++----- pysetup/helpers.py | 7 +++---- specs/fulu/beacon-chain.md | 9 +-------- specs/fulu/das-core.md | 4 +++- specs/fulu/p2p-interface.md | 2 +- .../fulu/merkle_proof/test_single_merkle_proof.py | 4 ++-- .../test/fulu/unittests/test_config_invariants.py | 12 ++++-------- tests/core/pyspec/eth2spec/test/helpers/blob.py | 9 --------- 9 files changed, 34 insertions(+), 43 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index cbc3b204da..9b4a36d93b 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -172,7 +172,6 @@ SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 VALIDATOR_CUSTODY_REQUIREMENT: 8 BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 -MAX_BLOBS_PER_BLOCK_FULU: 12 MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 # EIP7441 @@ -189,7 +188,13 @@ VIEW_FREEZE_DEADLINE: 9 # BLOBS BPO BLOB_SCHEDULE: - - EPOCH: 348618 - MAX_BLOBS_PER_BLOCK: 48 - - EPOCH: 355368 - MAX_BLOBS_PER_BLOCK: 56 + - EPOCH: 269568 + MAX_BLOBS_PER_BLOCK: 6 + - EPOCH: 364032 + MAX_BLOBS_PER_BLOCK: 9 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 18 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 36 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 72 \ No newline at end of file diff --git a/configs/minimal.yaml b/configs/minimal.yaml index eba54f9353..d02f171b65 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -169,7 +169,6 @@ SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 VALIDATOR_CUSTODY_REQUIREMENT: 8 BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 -MAX_BLOBS_PER_BLOCK_FULU: 12 MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 # EIP7441 @@ -186,7 +185,13 @@ VIEW_FREEZE_DEADLINE: 3 # BLOBS BPO BLOB_SCHEDULE: - - EPOCH: 348618 - MAX_BLOBS_PER_BLOCK: 48 - - EPOCH: 355368 - MAX_BLOBS_PER_BLOCK: 56 + - EPOCH: 269568 + MAX_BLOBS_PER_BLOCK: 6 + - EPOCH: 364032 + MAX_BLOBS_PER_BLOCK: 9 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 18 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 36 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 72 diff --git a/pysetup/helpers.py b/pysetup/helpers.py index e1efae0036..7bd2c06a12 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -291,17 +291,16 @@ def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, Dict[str, st """ out: Dict[str, Union[str, Dict[str, str]]] = dict() for k, v in conf.items(): - if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE' or k == 'CONFIG_NAME'): + if isinstance(v, str) and (v.startswith("0x") or k == "PRESET_BASE" or k == "CONFIG_NAME"): # Represent byte data with string, to avoid misinterpretation as big-endian int. # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. out[k] = f"'{v}'" else: - if k == 'BLOB_SCHEDULE': + if k == "BLOB_SCHEDULE": blob_schedule = {} for BPO in v: - blob_schedule[BPO['EPOCH']] = {"MAX_BLOBS_PER_BLOCK": BPO['MAX_BLOBS_PER_BLOCK']} + blob_schedule[BPO["EPOCH"]] = {"MAX_BLOBS_PER_BLOCK": BPO["MAX_BLOBS_PER_BLOCK"]} out[k] = blob_schedule - else: out[k] = str(int(v)) return out \ No newline at end of file diff --git a/specs/fulu/beacon-chain.md b/specs/fulu/beacon-chain.md index 717282a056..3534969d31 100644 --- a/specs/fulu/beacon-chain.md +++ b/specs/fulu/beacon-chain.md @@ -6,7 +6,6 @@ - [Introduction](#introduction) - [Configuration](#configuration) - - [Execution](#execution) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Block processing](#block-processing) - [Execution payload](#execution-payload) @@ -20,12 +19,6 @@ ## Configuration -### Execution - -| Name | Value | Description | -| -------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------- | -| `MAX_BLOBS_PER_BLOCK_FULU` | `uint64(12)` | *[New in Fulu:EIP7594]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | - ## Beacon chain state transition function ### Block processing @@ -45,7 +38,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify commitments are under limit - assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_FULU # [Modified in Fulu:EIP7594] + assert len(body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state)) # Verify the execution payload is valid versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] assert execution_engine.verify_and_notify_new_payload( diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 981653d7de..9bad0c6f77 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -138,9 +138,11 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ```python def get_max_blobs_per_block(epoch: Epoch) -> int: - for entry in reversed(sorted(BLOB_SCHEDULE, key=lambda e: e['EPOCH'])): + assert epoch >= DENEB_FORK_EPOCH + for entry in reversed(sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"])): if entry["EPOCH"] < epoch: return entry["MAX_BLOBS_PER_BLOCK"] + return 0 ``` ### `compute_columns_for_custody_group` diff --git a/specs/fulu/p2p-interface.md b/specs/fulu/p2p-interface.md index 8b9a695210..fa84476b07 100644 --- a/specs/fulu/p2p-interface.md +++ b/specs/fulu/p2p-interface.md @@ -167,7 +167,7 @@ Some gossip meshes are upgraded in the Fulu fork to support upgraded types. *Updated validation* - _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- - i.e. validate that `len(signed_beacon_block.message.body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_FULU` + i.e. validate that `len(signed_beacon_block.message.body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state))` ##### Blob subnets diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py index 3ace0576f0..54d16b8cbb 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -82,7 +82,7 @@ def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): - blob_count = spec.config.MAX_BLOBS_PER_BLOCK_FULU // 2 + blob_count = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) // 2 rng = random.Random(2222) yield from _run_blob_kzg_commitments_merkle_proof_test( spec, state, rng=rng, blob_count=blob_count @@ -93,7 +93,7 @@ def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__max_blobs(spec, state): - max_blobs = spec.config.MAX_BLOBS_PER_BLOCK_FULU + max_blobs = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) rng = random.Random(3333) yield from _run_blob_kzg_commitments_merkle_proof_test( spec, state, rng=rng, blob_count=max_blobs diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index ec54a88145..b5c4dd12e9 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -3,13 +3,7 @@ spec_test, with_fulu_and_later, ) - -from eth2spec.test.helpers.constants import ( - MAINNET, - MINIMAL, -) -from eth2spec.test.context import with_presets -from eth2spec.test.context import spec_state_test, with_phases, FULU +from eth2spec.test.context import spec_state_test @with_fulu_and_later @@ -38,12 +32,14 @@ def test_polynomial_commitments_sampling(spec): @spec_test @single_phase def test_networking(spec): - assert spec.config.MAX_BLOBS_PER_BLOCK_FULU <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + assert spec.get_max_blobs_per_block(364032 + 1) <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK @with_fulu_and_later @spec_state_test def test_get_max_blobs(spec, state): + max_blobs = spec.get_max_blobs_per_block(0) + assert max_blobs == 0 max_blobs = spec.get_max_blobs_per_block(269568 + 1) assert max_blobs == 6 max_blobs = spec.get_max_blobs_per_block(364032 + 1) diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index 1b1272ddc5..87dab70aee 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -116,12 +116,3 @@ def get_sample_blob_tx(spec, blob_count=1, rng=random.Random(5566), is_valid_blo ) opaque_tx = bytes([0x03]) + encode(signed_blob_tx) return opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs - - -def get_max_blob_count(spec): - if is_post_fulu(spec): - return spec.config.MAX_BLOBS_PER_BLOCK_FULU - elif is_post_electra(spec): - return spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA - else: - return spec.config.MAX_BLOBS_PER_BLOCK From 6324589bebf7f61b5cb4ca7d73545331bef883c3 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 17:19:55 +0200 Subject: [PATCH 06/25] revamp get_max_blob_count --- configs/mainnet.yaml | 13 ++++++------ configs/minimal.yaml | 11 +++++----- pysetup/helpers.py | 19 +++++++++--------- specs/fulu/das-core.md | 4 ++-- .../test_process_execution_payload.py | 2 +- .../eth2spec/test/deneb/sanity/test_blocks.py | 10 +++++----- .../fulu/unittests/test_config_invariants.py | 20 ++++++++++++------- .../core/pyspec/eth2spec/test/helpers/blob.py | 9 +++++++++ 8 files changed, 50 insertions(+), 38 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 9b4a36d93b..6af73f688b 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -186,15 +186,14 @@ ATTESTATION_DEADLINE: 4 PROPOSER_INCLUSION_LIST_CUT_OFF: 11 VIEW_FREEZE_DEADLINE: 9 -# BLOBS BPO BLOB_SCHEDULE: - - EPOCH: 269568 + - EPOCH: 269568 # Deneb MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 364032 + - EPOCH: 364032 # Electra MAX_BLOBS_PER_BLOCK: 9 - - EPOCH: 18446744073709551615 + - EPOCH: 18446744073709551615 # BPO1 MAX_BLOBS_PER_BLOCK: 18 - - EPOCH: 18446744073709551615 + - EPOCH: 18446744073709551615 # BPO2 MAX_BLOBS_PER_BLOCK: 36 - - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 72 \ No newline at end of file + - EPOCH: 18446744073709551615 # BPO3 + MAX_BLOBS_PER_BLOCK: 72 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d02f171b65..77c72cee33 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -183,15 +183,14 @@ ATTESTATION_DEADLINE: 2 PROPOSER_INCLUSION_LIST_CUT_OFF: 5 VIEW_FREEZE_DEADLINE: 3 -# BLOBS BPO BLOB_SCHEDULE: - - EPOCH: 269568 + - EPOCH: 269568 # Deneb MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 364032 + - EPOCH: 364032 # Electra MAX_BLOBS_PER_BLOCK: 9 - - EPOCH: 18446744073709551615 + - EPOCH: 18446744073709551615 # BPO1 MAX_BLOBS_PER_BLOCK: 18 - - EPOCH: 18446744073709551615 + - EPOCH: 18446744073709551615 # BPO2 MAX_BLOBS_PER_BLOCK: 36 - - EPOCH: 18446744073709551615 + - EPOCH: 18446744073709551615 # BPO3 MAX_BLOBS_PER_BLOCK: 72 diff --git a/pysetup/helpers.py b/pysetup/helpers.py index 7bd2c06a12..022f892cab 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -1,5 +1,5 @@ import re -from typing import TypeVar, Dict, Union +from typing import TypeVar, Dict, Union, List import textwrap from functools import reduce @@ -84,13 +84,13 @@ def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str functions_spec = '\n\n\n'.join(functions.values()) # Access global dict of config vars for runtime configurables + # Ignore variable between quotes and doubles quotes for name in spec_object.config_vars.keys(): - functions_spec = re.sub(r"(? str: if isinstance(vardef, list): # A special case for list of records. - # TODO(jtraglia): make this better. indent = " " * 4 lines = [f"{name}=("] for d in vardef: @@ -285,7 +285,7 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: ) -def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, Dict[str, str]]]: +def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, List[Dict[str, str]]]]: """ Parses a dict of basic str/int/list types into a dict for insertion into the spec code. """ @@ -296,11 +296,10 @@ def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, Dict[str, st # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. out[k] = f"'{v}'" else: - if k == "BLOB_SCHEDULE": - blob_schedule = {} - for BPO in v: - blob_schedule[BPO["EPOCH"]] = {"MAX_BLOBS_PER_BLOCK": BPO["MAX_BLOBS_PER_BLOCK"]} - out[k] = blob_schedule + if isinstance(v, list): + # A special case for list of records + for entry in v: + out[k] = v else: out[k] = str(int(v)) - return out \ No newline at end of file + return out diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 9bad0c6f77..8deb581461 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -137,12 +137,12 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ### `get_max_blobs_per_block` ```python -def get_max_blobs_per_block(epoch: Epoch) -> int: +def get_max_blobs_per_block(epoch: Epoch) -> uint64: assert epoch >= DENEB_FORK_EPOCH for entry in reversed(sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"])): if entry["EPOCH"] < epoch: return entry["MAX_BLOBS_PER_BLOCK"] - return 0 + return uint64(0) ``` ### `compute_columns_for_custody_group` diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index f9bd5ee14f..b089a63f84 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -411,7 +411,7 @@ def test_invalid_exceed_max_blobs_per_block(spec, state): execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( - spec, blob_count=get_max_blob_count(spec) + 1 + spec, blob_count=get_max_blob_count(spec, state) + 1 ) execution_payload.transactions = [opaque_tx] diff --git a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py index a2baf73984..d6f0d537f8 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py @@ -84,28 +84,28 @@ def test_one_blob_two_txs(spec, state): @with_deneb_until_eip7732 @spec_state_test def test_one_blob_max_txs(spec, state): - yield from run_block_with_blobs(spec, state, blob_count=1, tx_count=get_max_blob_count(spec)) + yield from run_block_with_blobs(spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state)) @with_deneb_until_eip7732 @spec_state_test def test_invalid_one_blob_max_plus_one_txs(spec, state): yield from run_block_with_blobs( - spec, state, blob_count=1, tx_count=get_max_blob_count(spec) + 1, valid=False + spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state) + 1, valid=False ) @with_deneb_until_eip7732 @spec_state_test def test_max_blobs_per_block(spec, state): - yield from run_block_with_blobs(spec, state, blob_count=get_max_blob_count(spec)) + yield from run_block_with_blobs(spec, state, blob_count=get_max_blob_count(spec, state)) @with_deneb_until_eip7732 @spec_state_test def test_invalid_max_blobs_per_block_two_txs(spec, state): yield from run_block_with_blobs( - spec, state, blob_count=get_max_blob_count(spec), tx_count=2, valid=False + spec, state, blob_count=get_max_blob_count(spec, state), tx_count=2, valid=False ) @@ -113,7 +113,7 @@ def test_invalid_max_blobs_per_block_two_txs(spec, state): @spec_state_test def test_invalid_exceed_max_blobs_per_block(spec, state): yield from run_block_with_blobs( - spec, state, blob_count=get_max_blob_count(spec) + 1, valid=False + spec, state, blob_count=get_max_blob_count(spec, state) + 1, valid=False ) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index b5c4dd12e9..6358aa503f 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -1,10 +1,13 @@ from eth2spec.test.context import ( single_phase, spec_test, + with_presets, with_fulu_and_later, ) from eth2spec.test.context import spec_state_test - +from eth2spec.test.helpers.constants import ( + MAINNET, +) @with_fulu_and_later @spec_test @@ -37,10 +40,13 @@ def test_networking(spec): @with_fulu_and_later @spec_state_test +@with_presets([MAINNET], reason="to have fork epoch number") def test_get_max_blobs(spec, state): - max_blobs = spec.get_max_blobs_per_block(0) - assert max_blobs == 0 - max_blobs = spec.get_max_blobs_per_block(269568 + 1) - assert max_blobs == 6 - max_blobs = spec.get_max_blobs_per_block(364032 + 1) - assert max_blobs == 9 + # Check that after Deneb fork, blob count goes to 6 + assert 6 == spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH + 1) + + # Check that until Electra fork, blob count is still 6 + assert 6 == spec.get_max_blobs_per_block(spec.config.ELECTRA_FORK_EPOCH) + + # Check that after Electra fork, blob count goes to 9 + assert 9 == spec.get_max_blobs_per_block(spec.config.ELECTRA_FORK_EPOCH + 1) diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index 87dab70aee..296e42a48b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -116,3 +116,12 @@ def get_sample_blob_tx(spec, blob_count=1, rng=random.Random(5566), is_valid_blo ) opaque_tx = bytes([0x03]) + encode(signed_blob_tx) return opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs + + +def get_max_blob_count(spec, state): + if is_post_fulu(spec): + return spec.get_max_blobs_per_block(spec.get_current_epoch(state)) + elif is_post_electra(spec): + return spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA + else: + return spec.config.MAX_BLOBS_PER_BLOCK From f1deb4240e46c697f00ffc21ad8839cc0c64abb1 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 17 Apr 2025 18:54:26 +0200 Subject: [PATCH 07/25] address Justin comments --- specs/fulu/beacon-chain.md | 2 +- specs/fulu/das-core.md | 4 +-- .../eth2spec/test/deneb/sanity/test_blocks.py | 4 ++- .../fulu/unittests/test_config_invariants.py | 26 +++++++++++++------ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/specs/fulu/beacon-chain.md b/specs/fulu/beacon-chain.md index 3534969d31..1f34b40be9 100644 --- a/specs/fulu/beacon-chain.md +++ b/specs/fulu/beacon-chain.md @@ -38,7 +38,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify commitments are under limit - assert len(body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state)) + assert len(body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state)) # [Modified in Fulu:EIP7594] # Verify the execution payload is valid versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] assert execution_engine.verify_and_notify_new_payload( diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 8deb581461..b35dd091d7 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -139,8 +139,8 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ```python def get_max_blobs_per_block(epoch: Epoch) -> uint64: assert epoch >= DENEB_FORK_EPOCH - for entry in reversed(sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"])): - if entry["EPOCH"] < 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 uint64(0) ``` diff --git a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py index d6f0d537f8..1cf5437966 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py @@ -84,7 +84,9 @@ def test_one_blob_two_txs(spec, state): @with_deneb_until_eip7732 @spec_state_test def test_one_blob_max_txs(spec, state): - yield from run_block_with_blobs(spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state)) + yield from run_block_with_blobs( + spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state) + ) @with_deneb_until_eip7732 diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index 6358aa503f..eea59d91f4 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -9,6 +9,7 @@ MAINNET, ) + @with_fulu_and_later @spec_test @single_phase @@ -42,11 +43,20 @@ def test_networking(spec): @spec_state_test @with_presets([MAINNET], reason="to have fork epoch number") def test_get_max_blobs(spec, state): - # Check that after Deneb fork, blob count goes to 6 - assert 6 == spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH + 1) - - # Check that until Electra fork, blob count is still 6 - assert 6 == spec.get_max_blobs_per_block(spec.config.ELECTRA_FORK_EPOCH) - - # Check that after Electra fork, blob count goes to 9 - assert 9 == spec.get_max_blobs_per_block(spec.config.ELECTRA_FORK_EPOCH + 1) + # Check that before Deneb fork there is no blob count + try: + spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1) + except AssertionError: + pass + # Check that after Deneb fork, blob count is equal to MAX_BLOBS_PER_BLOCK (6) + assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( + spec.config.DENEB_FORK_EPOCH + ) + # Check that until Electra fork, blob count is still MAX_BLOBS_PER_BLOCK (6) + assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( + spec.config.ELECTRA_FORK_EPOCH - 1 + ) + # Check that after Electra fork, blob count goes to MAX_BLOBS_PER_BLOCK_ELECTRA (9) + assert spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA == spec.get_max_blobs_per_block( + spec.config.ELECTRA_FORK_EPOCH + ) From 5b69bc2de334ac8494576c4b7caa864bf5a44c65 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 13:10:21 -0500 Subject: [PATCH 08/25] Simplify blob schedule table --- specs/fulu/das-core.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index b35dd091d7..46c7db7709 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -75,13 +75,13 @@ The following values are (non-configurable) constants used throughout the specif -| Epoch | Max Blobs Per Block | Description | -| -------------------------------------- | ------------------- | ------------------------------------------------------------------------- | -| `Epoch(269568)` **Deneb** | `uint64(6)` | Starting at epoch `269568`, the limit is `6` blobs | -| `Epoch(364032)` **Electra** | `uint64(9)` | Starting at epoch `364032`, the limit is `9` blobs | -| `Epoch(18446744073709551615)` **BPO1** | `uint64(18)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `18` blobs | -| `Epoch(18446744073709551615)` **BPO2** | `uint64(36)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `36` blobs | -| `Epoch(18446744073709551615)` **BPO3** | `uint64(72)` | Starting at epoch `18446744073709551615` **TBD**, the limit is `72` blobs | +| Epoch | Max Blobs Per Block | Description | +| ----------------------------- | ------------------- | --------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | +| `Epoch(18446744073709551615)` | `uint64(18)` | The limit is raised to `18` blobs | +| `Epoch(18446744073709551615)` | `uint64(36)` | The limit is raised to `36` blobs | +| `Epoch(18446744073709551615)` | `uint64(72)` | The limit is raised to `72` blobs | ### Containers From 1cf0f032e0a9a6274b9ffc707e1ab2820d9b9d35 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 13:21:16 -0500 Subject: [PATCH 09/25] Clean up parse_config_vars function --- pysetup/helpers.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pysetup/helpers.py b/pysetup/helpers.py index 022f892cab..0f52e3d5ba 100644 --- a/pysetup/helpers.py +++ b/pysetup/helpers.py @@ -289,17 +289,15 @@ def parse_config_vars(conf: Dict[str, str]) -> Dict[str, Union[str, List[Dict[st """ Parses a dict of basic str/int/list types into a dict for insertion into the spec code. """ - out: Dict[str, Union[str, Dict[str, str]]] = dict() + out: Dict[str, Union[str, List[Dict[str, str]]]] = dict() for k, v in conf.items(): - if isinstance(v, str) and (v.startswith("0x") or k == "PRESET_BASE" or k == "CONFIG_NAME"): + if isinstance(v, list): + # A special case for list of records + out[k] = v + elif isinstance(v, str) and (v.startswith("0x") or k == "PRESET_BASE" or k == "CONFIG_NAME"): # Represent byte data with string, to avoid misinterpretation as big-endian int. # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. out[k] = f"'{v}'" else: - if isinstance(v, list): - # A special case for list of records - for entry in v: - out[k] = v - else: - out[k] = str(int(v)) + out[k] = str(int(v)) return out From c0d859525e205fef8297d0346ca52e8e5cd72ad1 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 14:27:01 -0500 Subject: [PATCH 10/25] Improve testing just a little --- .../fulu/unittests/test_config_invariants.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index eea59d91f4..968efb9af5 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -1,10 +1,11 @@ from eth2spec.test.context import ( single_phase, spec_test, + spec_state_test, with_presets, with_fulu_and_later, + expect_assertion_error, ) -from eth2spec.test.context import spec_state_test from eth2spec.test.helpers.constants import ( MAINNET, ) @@ -35,28 +36,31 @@ def test_polynomial_commitments_sampling(spec): @with_fulu_and_later @spec_test @single_phase -def test_networking(spec): - assert spec.get_max_blobs_per_block(364032 + 1) <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK +@with_presets([MAINNET], reason="to have fork epoch number") +def test_blob_schedule(spec): + for entry in spec.config.BLOB_SCHEDULE: + # Check that all epochs are post-Deneb + assert entry["EPOCH"] >= spec.config.DENEB_FORK_EPOCH + # Check that all blob counts are less than the limit + assert entry["MAX_BLOBS_PER_BLOCK"] <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK @with_fulu_and_later -@spec_state_test +@spec_test +@single_phase @with_presets([MAINNET], reason="to have fork epoch number") -def test_get_max_blobs(spec, state): +def test_get_max_blobs(spec): # Check that before Deneb fork there is no blob count - try: - spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1) - except AssertionError: - pass - # Check that after Deneb fork, blob count is equal to MAX_BLOBS_PER_BLOCK (6) + expect_assertion_error(lambda: spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1)) + # Check that at the Deneb fork, blob count is equal to MAX_BLOBS_PER_BLOCK assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( spec.config.DENEB_FORK_EPOCH ) - # Check that until Electra fork, blob count is still MAX_BLOBS_PER_BLOCK (6) + # Check that until Electra fork, blob count is still MAX_BLOBS_PER_BLOCK assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( spec.config.ELECTRA_FORK_EPOCH - 1 ) - # Check that after Electra fork, blob count goes to MAX_BLOBS_PER_BLOCK_ELECTRA (9) + # Check that at the Electra fork, blob count goes to MAX_BLOBS_PER_BLOCK_ELECTRA assert spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA == spec.get_max_blobs_per_block( spec.config.ELECTRA_FORK_EPOCH ) From d3c21d80083b3a8cba77cb869d184c3b2a96cd5f Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 16:23:30 -0500 Subject: [PATCH 11/25] Check/overwrite blob schedule with config file --- configs/minimal.yaml | 11 ++++++----- setup.py | 17 +++++++++++++++++ .../test_process_execution_payload.py | 5 ++--- .../merkle_proof/test_single_merkle_proof.py | 4 ++-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index e239c4b023..c6a3423d68 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -187,14 +187,15 @@ MAX_REQUEST_INCLUSION_LIST: 16 # 2**13 (= 8192) MAX_BYTES_PER_INCLUSION_LIST: 8192 +# [customized] BLOB_SCHEDULE: - - EPOCH: 269568 # Deneb + - EPOCH: 18446744073709551615 # Deneb MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 364032 # Electra + - EPOCH: 18446744073709551615 # Electra MAX_BLOBS_PER_BLOCK: 9 - EPOCH: 18446744073709551615 # BPO1 - MAX_BLOBS_PER_BLOCK: 18 + MAX_BLOBS_PER_BLOCK: 12 - EPOCH: 18446744073709551615 # BPO2 - MAX_BLOBS_PER_BLOCK: 36 + MAX_BLOBS_PER_BLOCK: 15 - EPOCH: 18446744073709551615 # BPO3 - MAX_BLOBS_PER_BLOCK: 72 + MAX_BLOBS_PER_BLOCK: 18 diff --git a/setup.py b/setup.py index fd2bb2144a..443f3cd7bf 100644 --- a/setup.py +++ b/setup.py @@ -337,6 +337,23 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr # After processing the list of records table, set this to None so # that the next table is processed appropriately if list_of_records is not None: + if list_of_records_name == "BLOB_SCHEDULE": + # A bit of a hack. Check that the blob schedule in the config matches + # the blob schedule in the specs. For minimal, overwrite the values. + blob_schedule_from_config = [ + { + "EPOCH": f'Epoch({entry["EPOCH"]})', + "MAX_BLOBS_PER_BLOCK": f'uint64({entry["MAX_BLOBS_PER_BLOCK"]})' + } + for entry in config["BLOB_SCHEDULE"] + ] + if preset_name == "mainnet": + assert list_of_records == blob_schedule_from_config, \ + f"blob schedule mismatch: {list_of_records} vs {blob_schedule_from_config}" + elif preset_name == "minimal": + list_of_records = blob_schedule_from_config + + # Set the config variable and reset the global variable config_vars[list_of_records_name] = list_of_records list_of_records = None diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index b089a63f84..2a296a0e43 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -410,9 +410,8 @@ def test_invalid_correct_input__execution_invalid(spec, state): def test_invalid_exceed_max_blobs_per_block(spec, state): execution_payload = build_empty_execution_payload(spec, state) - opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( - spec, blob_count=get_max_blob_count(spec, state) + 1 - ) + max_blobs = max(e["MAX_BLOBS_PER_BLOCK"] for e in spec.config.BLOB_SCHEDULE) + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=max_blobs + 1) execution_payload.transactions = [opaque_tx] execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py index 54d16b8cbb..1ad9b8d11e 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -82,7 +82,7 @@ def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): - blob_count = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) // 2 + blob_count = 2 rng = random.Random(2222) yield from _run_blob_kzg_commitments_merkle_proof_test( spec, state, rng=rng, blob_count=blob_count @@ -93,7 +93,7 @@ def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__max_blobs(spec, state): - max_blobs = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) + max_blobs = max(e["MAX_BLOBS_PER_BLOCK"] for e in spec.config.BLOB_SCHEDULE) rng = random.Random(3333) yield from _run_blob_kzg_commitments_merkle_proof_test( spec, state, rng=rng, blob_count=max_blobs From 5501ac1d711c614671770095da972eac4a0162f0 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 16:41:38 -0500 Subject: [PATCH 12/25] Remove assert and fix tests --- specs/fulu/das-core.md | 1 - .../test/fulu/merkle_proof/test_single_merkle_proof.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 46c7db7709..4bfae80de8 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -138,7 +138,6 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ```python def get_max_blobs_per_block(epoch: Epoch) -> uint64: - assert epoch >= DENEB_FORK_EPOCH for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True): if epoch >= entry["EPOCH"]: return entry["MAX_BLOBS_PER_BLOCK"] diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py index 1ad9b8d11e..28b36d55c5 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -82,10 +82,9 @@ def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): - blob_count = 2 rng = random.Random(2222) yield from _run_blob_kzg_commitments_merkle_proof_test( - spec, state, rng=rng, blob_count=blob_count + spec, state, rng=rng, blob_count=spec.get_max_blobs_per_block(spec.get_current_epoch(state)) // 2 ) @@ -93,8 +92,7 @@ def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__max_blobs(spec, state): - max_blobs = max(e["MAX_BLOBS_PER_BLOCK"] for e in spec.config.BLOB_SCHEDULE) rng = random.Random(3333) yield from _run_blob_kzg_commitments_merkle_proof_test( - spec, state, rng=rng, blob_count=max_blobs + spec, state, rng=rng, blob_count=spec.get_max_blobs_per_block(spec.get_current_epoch(state)) ) From d2a425355321a30cab353b82d4ef3365ad0881dd Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 17 Apr 2025 21:55:53 -0500 Subject: [PATCH 13/25] Fix lint & update minimal blob schedule --- configs/minimal.yaml | 4 ++-- .../block_processing/test_process_execution_payload.py | 5 +++-- .../test/fulu/merkle_proof/test_single_merkle_proof.py | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index c6a3423d68..b731e49bbf 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -189,9 +189,9 @@ MAX_BYTES_PER_INCLUSION_LIST: 8192 # [customized] BLOB_SCHEDULE: - - EPOCH: 18446744073709551615 # Deneb + - EPOCH: 0 # Deneb MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 18446744073709551615 # Electra + - EPOCH: 1 # Electra MAX_BLOBS_PER_BLOCK: 9 - EPOCH: 18446744073709551615 # BPO1 MAX_BLOBS_PER_BLOCK: 12 diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index 2a296a0e43..b089a63f84 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -410,8 +410,9 @@ def test_invalid_correct_input__execution_invalid(spec, state): def test_invalid_exceed_max_blobs_per_block(spec, state): execution_payload = build_empty_execution_payload(spec, state) - max_blobs = max(e["MAX_BLOBS_PER_BLOCK"] for e in spec.config.BLOB_SCHEDULE) - opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=max_blobs + 1) + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( + spec, blob_count=get_max_blob_count(spec, state) + 1 + ) execution_payload.transactions = [opaque_tx] execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py index 28b36d55c5..54d16b8cbb 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -82,9 +82,10 @@ def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): + blob_count = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) // 2 rng = random.Random(2222) yield from _run_blob_kzg_commitments_merkle_proof_test( - spec, state, rng=rng, blob_count=spec.get_max_blobs_per_block(spec.get_current_epoch(state)) // 2 + spec, state, rng=rng, blob_count=blob_count ) @@ -92,7 +93,8 @@ def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): @with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__max_blobs(spec, state): + max_blobs = spec.get_max_blobs_per_block(spec.get_current_epoch(state)) rng = random.Random(3333) yield from _run_blob_kzg_commitments_merkle_proof_test( - spec, state, rng=rng, blob_count=spec.get_max_blobs_per_block(spec.get_current_epoch(state)) + spec, state, rng=rng, blob_count=max_blobs ) From 75163d67f69650540f799ad5ae951bd09d20934a Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Fri, 18 Apr 2025 09:40:11 -0500 Subject: [PATCH 14/25] Improve list of records parsing --- setup.py | 78 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/setup.py b/setup.py index 443f3cd7bf..7dc617c83d 100644 --- a/setup.py +++ b/setup.py @@ -258,7 +258,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr ssz_objects[current_name] = "\n".join(line.rstrip() for line in source.splitlines()) else: raise Exception("unrecognized python code element: " + source) - elif isinstance(child, Table): + elif isinstance(child, Table) and list_of_records is not None: list_of_records_header = None for i, row in enumerate(child.children): # This will start as an empty list when there is a comment, @@ -266,20 +266,56 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr # the table, we will reset this to None. if list_of_records is not None: if i == 0: - # Save the table header, this will be used for field names. - # Skip the last item, which is the description. + # Save the table header, this will be used for field names + # Skip the last item, which is the description list_of_records_header = [ - # Convert the title to SNAKE_CASE + # Convert the titles to SNAKE_CASE re.sub(r'\s+', '_', value.children[0].children.upper()) for value in row.children[:-1] ] - continue - list_of_records.append({ - list_of_records_header[i]: value.children[0].children - for i, value in enumerate(row.children[:-1]) - }) - continue + else: + # Add the row entry to our list of records + list_of_records.append({ + list_of_records_header[i]: value.children[0].children + for i, value in enumerate(row.children[:-1]) + }) + + # Make a type map from the spec definition + # We'll apply this to the file config (ie mainnet.yaml) + type_map: dict[str,str] = {} + pattern = re.compile(r'^(\w+)\(.*\)$') + for entry in list_of_records: + for k, v in entry.items(): + m = pattern.match(v) + if m: + type_map[k] = m.group(1) + + # Apply the types to the file config + list_of_records_config: list[dict[str,str]] = [] + for entry in config[list_of_records_name]: + new_entry: dict[str,str] = {} + for k, v in entry.items(): + ctor = type_map.get(k) + if ctor: + new_entry[k] = f"{ctor}({v})" + else: + new_entry[k] = v + list_of_records_config.append(new_entry) + # For mainnet, check that the spec config & file config are the same + # For minimal, we expect this to be different; just use the file config + if preset_name == "mainnet": + assert list_of_records == list_of_records_config, \ + f"list of records mismatch: {list_of_records} vs {list_of_records_config}" + elif preset_name == "minimal": + list_of_records = list_of_records_config + + # Set the config variable and reset the global variable + config_vars[list_of_records_name] = list_of_records + list_of_records = None + + elif isinstance(child, Table): + for row in child.children: cells = row.children if len(cells) >= 2: name_cell = cells[0] @@ -334,28 +370,6 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr preset_dep_constant_vars[name] = value_def else: constant_vars[name] = value_def - # After processing the list of records table, set this to None so - # that the next table is processed appropriately - if list_of_records is not None: - if list_of_records_name == "BLOB_SCHEDULE": - # A bit of a hack. Check that the blob schedule in the config matches - # the blob schedule in the specs. For minimal, overwrite the values. - blob_schedule_from_config = [ - { - "EPOCH": f'Epoch({entry["EPOCH"]})', - "MAX_BLOBS_PER_BLOCK": f'uint64({entry["MAX_BLOBS_PER_BLOCK"]})' - } - for entry in config["BLOB_SCHEDULE"] - ] - if preset_name == "mainnet": - assert list_of_records == blob_schedule_from_config, \ - f"blob schedule mismatch: {list_of_records} vs {blob_schedule_from_config}" - elif preset_name == "minimal": - list_of_records = blob_schedule_from_config - - # Set the config variable and reset the global variable - config_vars[list_of_records_name] = list_of_records - list_of_records = None elif isinstance(child, HTMLBlock): if child.body.strip() == "": From be5578bef3e1f697d58646f5e8eb94173f1aaf9d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 22 Apr 2025 16:59:33 +0200 Subject: [PATCH 15/25] remove BPO --- configs/mainnet.yaml | 6 ------ configs/minimal.yaml | 6 ------ specs/fulu/das-core.md | 3 --- 3 files changed, 15 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index f03b712c1c..9aa0342686 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -195,9 +195,3 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 6 - EPOCH: 364032 # Electra MAX_BLOBS_PER_BLOCK: 9 - - EPOCH: 18446744073709551615 # BPO1 - MAX_BLOBS_PER_BLOCK: 18 - - EPOCH: 18446744073709551615 # BPO2 - MAX_BLOBS_PER_BLOCK: 36 - - EPOCH: 18446744073709551615 # BPO3 - MAX_BLOBS_PER_BLOCK: 72 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index b731e49bbf..9277fcd05b 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -193,9 +193,3 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 6 - EPOCH: 1 # Electra MAX_BLOBS_PER_BLOCK: 9 - - EPOCH: 18446744073709551615 # BPO1 - MAX_BLOBS_PER_BLOCK: 12 - - EPOCH: 18446744073709551615 # BPO2 - MAX_BLOBS_PER_BLOCK: 15 - - EPOCH: 18446744073709551615 # BPO3 - MAX_BLOBS_PER_BLOCK: 18 diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 4bfae80de8..9af10e0418 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -79,9 +79,6 @@ The following values are (non-configurable) constants used throughout the specif | ----------------------------- | ------------------- | --------------------------------- | | `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | | `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | -| `Epoch(18446744073709551615)` | `uint64(18)` | The limit is raised to `18` blobs | -| `Epoch(18446744073709551615)` | `uint64(36)` | The limit is raised to `36` blobs | -| `Epoch(18446744073709551615)` | `uint64(72)` | The limit is raised to `72` blobs | ### Containers From 918dcb15c910a58f3fb493d27a50a53f80b876a9 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 22 Apr 2025 17:19:21 +0200 Subject: [PATCH 16/25] fix test_incorrect_blob_tx_type --- .../deneb/block_processing/test_process_execution_payload.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index b089a63f84..1beacbbc97 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -124,6 +124,7 @@ def test_incorrect_blob_tx_type(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) From 2752acc33ab6c4cdac0b40ceef5ebf389edff3e5 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 22 Apr 2025 17:30:36 +0200 Subject: [PATCH 17/25] fix block processing tests --- .../test_process_execution_payload.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index 1beacbbc97..9bd80a6a0a 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -148,6 +148,7 @@ def test_incorrect_transaction_length_1_extra_byte(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -170,6 +171,7 @@ def test_incorrect_transaction_length_1_byte_short(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -192,6 +194,7 @@ def test_incorrect_transaction_length_empty(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -214,6 +217,7 @@ def test_incorrect_transaction_length_32_extra_bytes(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -236,6 +240,7 @@ def test_no_transactions_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) _, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -257,6 +262,7 @@ def test_incorrect_commitment(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -279,6 +285,7 @@ def test_no_commitments_for_transactions(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -301,6 +308,7 @@ def test_incorrect_commitments_order(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -323,6 +331,7 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) # the blob transaction is invalid, because the EL verifies that the tx contains at least one blob @@ -345,6 +354,7 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): @with_deneb_and_later @spec_state_test def test_incorrect_block_hash(spec, state): + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -367,6 +377,7 @@ def test_zeroed_commitment(spec, state): """ The blob is invalid, but the commitment is in correct form. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( @@ -391,6 +402,7 @@ def test_invalid_correct_input__execution_invalid(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -409,6 +421,7 @@ def test_invalid_correct_input__execution_invalid(spec, state): @with_deneb_and_later @spec_state_test def test_invalid_exceed_max_blobs_per_block(spec, state): + state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( From 6d1027d9073dddf841af1b67798026a2aaa32e8e Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 22 Apr 2025 18:16:51 +0200 Subject: [PATCH 18/25] linting changes --- specs/fulu/das-core.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 9af10e0418..53053c278b 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -75,10 +75,10 @@ The following values are (non-configurable) constants used throughout the specif -| Epoch | Max Blobs Per Block | Description | -| ----------------------------- | ------------------- | --------------------------------- | -| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | -| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | +| Epoch | Max Blobs Per Block | Description | +| --------------------------- | ------------------- | -------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | ### Containers From 80e9f23c286fa2ff533facde7402bea284168fe6 Mon Sep 17 00:00:00 2001 From: Gabriel Astieres Date: Wed, 23 Apr 2025 11:02:08 +0200 Subject: [PATCH 19/25] fix test_get_max_blobs --- .../eth2spec/test/fulu/unittests/test_config_invariants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index 968efb9af5..c8dc132d94 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -51,7 +51,7 @@ def test_blob_schedule(spec): @with_presets([MAINNET], reason="to have fork epoch number") def test_get_max_blobs(spec): # Check that before Deneb fork there is no blob count - expect_assertion_error(lambda: spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1)) + assert 0 == spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1) # Check that at the Deneb fork, blob count is equal to MAX_BLOBS_PER_BLOCK assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( spec.config.DENEB_FORK_EPOCH From 5f4750181eeade9de51f8aab99771c24ca870b4d Mon Sep 17 00:00:00 2001 From: Gabriel Astieres Date: Wed, 23 Apr 2025 11:33:18 +0200 Subject: [PATCH 20/25] hardcode fork epoch in test --- .../test_process_execution_payload.py | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index 9bd80a6a0a..c161aade4a 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -124,7 +124,8 @@ def test_incorrect_blob_tx_type(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -148,7 +149,8 @@ def test_incorrect_transaction_length_1_extra_byte(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -171,7 +173,8 @@ def test_incorrect_transaction_length_1_byte_short(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -194,7 +197,8 @@ def test_incorrect_transaction_length_empty(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -217,7 +221,8 @@ def test_incorrect_transaction_length_32_extra_bytes(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -240,7 +245,8 @@ def test_no_transactions_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) _, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -262,7 +268,8 @@ def test_incorrect_commitment(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -285,7 +292,8 @@ def test_no_commitments_for_transactions(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -308,7 +316,8 @@ def test_incorrect_commitments_order(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -331,7 +340,8 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) # the blob transaction is invalid, because the EL verifies that the tx contains at least one blob @@ -354,7 +364,8 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): @with_deneb_and_later @spec_state_test def test_incorrect_block_hash(spec, state): - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -377,7 +388,8 @@ def test_zeroed_commitment(spec, state): """ The blob is invalid, but the commitment is in correct form. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( @@ -402,7 +414,8 @@ def test_invalid_correct_input__execution_invalid(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -421,7 +434,8 @@ def test_invalid_correct_input__execution_invalid(spec, state): @with_deneb_and_later @spec_state_test def test_invalid_exceed_max_blobs_per_block(spec, state): - state.slot += spec.config.DENEB_FORK_EPOCH * spec.SLOTS_PER_EPOCH + # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets + state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( From e2dfe47121b184f4d01c45a1a71087f5a6617034 Mon Sep 17 00:00:00 2001 From: Gabriel Astieres Date: Wed, 23 Apr 2025 18:57:11 +0200 Subject: [PATCH 21/25] Revamp max blobs getter to default to electra limit --- configs/mainnet.yaml | 16 ++++++++--- configs/minimal.yaml | 17 +++++++---- specs/fulu/das-core.md | 13 +++++---- .../test_process_execution_payload.py | 28 ------------------- .../fulu/unittests/test_config_invariants.py | 23 +-------------- 5 files changed, 32 insertions(+), 65 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 9aa0342686..7838d35f17 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -190,8 +190,16 @@ MAX_REQUEST_INCLUSION_LIST: 16 # 2**13 (= 8192) MAX_BYTES_PER_INCLUSION_LIST: 8192 +# Blob Scheduling +# --------------------------------------------------------------- + BLOB_SCHEDULE: - - EPOCH: 269568 # Deneb - MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 364032 # Electra - MAX_BLOBS_PER_BLOCK: 9 + # Placeholder BPO1 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 10 + # Placeholder BPO2 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 11 + # Placeholder BPO3 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 12 \ No newline at end of file diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 9277fcd05b..de38514065 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -187,9 +187,16 @@ MAX_REQUEST_INCLUSION_LIST: 16 # 2**13 (= 8192) MAX_BYTES_PER_INCLUSION_LIST: 8192 -# [customized] +# Blob Scheduling +# --------------------------------------------------------------- + BLOB_SCHEDULE: - - EPOCH: 0 # Deneb - MAX_BLOBS_PER_BLOCK: 6 - - EPOCH: 1 # Electra - MAX_BLOBS_PER_BLOCK: 9 + # Placeholder BPO1 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 10 + # Placeholder BPO2 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 11 + # Placeholder BPO3 + - EPOCH: 18446744073709551615 + MAX_BLOBS_PER_BLOCK: 12 \ No newline at end of file diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index 53053c278b..d6ccef542a 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -71,14 +71,15 @@ The following values are (non-configurable) constants used throughout the specif ### Blob schedule -*[New in EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. +*[New in EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. For epoch values before those defined in the table, the default blob limit is `MAX_BLOBS_PER_BLOCK_ELECTRA`. -| Epoch | Max Blobs Per Block | Description | -| --------------------------- | ------------------- | -------------------------------- | -| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | -| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | +| Epoch | Max Blobs Per Block | Description | +| ----------------------------- | ------------------- | --------------------------------- | +| `Epoch(18446744073709551615)` | `uint64(10)` | The limit is raised to `10` blobs | +| `Epoch(18446744073709551615)` | `uint64(11)` | The limit is raised to `11` blobs | +| `Epoch(18446744073709551615)` | `uint64(12)` | The limit is raised to `12` blobs | ### Containers @@ -138,7 +139,7 @@ def get_max_blobs_per_block(epoch: Epoch) -> uint64: for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True): if epoch >= entry["EPOCH"]: return entry["MAX_BLOBS_PER_BLOCK"] - return uint64(0) + return MAX_BLOBS_PER_BLOCK_ELECTRA ``` ### `compute_columns_for_custody_group` diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index c161aade4a..b089a63f84 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -124,8 +124,6 @@ def test_incorrect_blob_tx_type(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -149,8 +147,6 @@ def test_incorrect_transaction_length_1_extra_byte(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -173,8 +169,6 @@ def test_incorrect_transaction_length_1_byte_short(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -197,8 +191,6 @@ def test_incorrect_transaction_length_empty(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -221,8 +213,6 @@ def test_incorrect_transaction_length_32_extra_bytes(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -245,8 +235,6 @@ def test_no_transactions_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) _, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -268,8 +256,6 @@ def test_incorrect_commitment(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -292,8 +278,6 @@ def test_no_commitments_for_transactions(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -316,8 +300,6 @@ def test_incorrect_commitments_order(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) @@ -340,8 +322,6 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) # the blob transaction is invalid, because the EL verifies that the tx contains at least one blob @@ -364,8 +344,6 @@ def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): @with_deneb_and_later @spec_state_test def test_incorrect_block_hash(spec, state): - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -388,8 +366,6 @@ def test_zeroed_commitment(spec, state): """ The blob is invalid, but the commitment is in correct form. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( @@ -414,8 +390,6 @@ def test_invalid_correct_input__execution_invalid(spec, state): """ The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default. """ - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) @@ -434,8 +408,6 @@ def test_invalid_correct_input__execution_invalid(spec, state): @with_deneb_and_later @spec_state_test def test_invalid_exceed_max_blobs_per_block(spec, state): - # Hardcode Deneb Fork Epoch to comply with both minimal and mainnet presets - state.slot += 269568 * spec.SLOTS_PER_EPOCH execution_payload = build_empty_execution_payload(spec, state) opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index c8dc132d94..2d9bd4f73e 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -40,27 +40,6 @@ def test_polynomial_commitments_sampling(spec): def test_blob_schedule(spec): for entry in spec.config.BLOB_SCHEDULE: # Check that all epochs are post-Deneb - assert entry["EPOCH"] >= spec.config.DENEB_FORK_EPOCH + assert entry["EPOCH"] >= spec.config.FULU_FORK_EPOCH # Check that all blob counts are less than the limit assert entry["MAX_BLOBS_PER_BLOCK"] <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK - - -@with_fulu_and_later -@spec_test -@single_phase -@with_presets([MAINNET], reason="to have fork epoch number") -def test_get_max_blobs(spec): - # Check that before Deneb fork there is no blob count - assert 0 == spec.get_max_blobs_per_block(spec.config.DENEB_FORK_EPOCH - 1) - # Check that at the Deneb fork, blob count is equal to MAX_BLOBS_PER_BLOCK - assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( - spec.config.DENEB_FORK_EPOCH - ) - # Check that until Electra fork, blob count is still MAX_BLOBS_PER_BLOCK - assert spec.config.MAX_BLOBS_PER_BLOCK == spec.get_max_blobs_per_block( - spec.config.ELECTRA_FORK_EPOCH - 1 - ) - # Check that at the Electra fork, blob count goes to MAX_BLOBS_PER_BLOCK_ELECTRA - assert spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA == spec.get_max_blobs_per_block( - spec.config.ELECTRA_FORK_EPOCH - ) From c0088d15d9ab8488cad7e78f8855af1158b99995 Mon Sep 17 00:00:00 2001 From: Gabriel Astieres Date: Thu, 24 Apr 2025 10:21:02 +0200 Subject: [PATCH 22/25] add EOF lines --- configs/mainnet.yaml | 2 +- configs/minimal.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 7838d35f17..80f80a35e4 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -202,4 +202,4 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 11 # Placeholder BPO3 - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 12 \ No newline at end of file + MAX_BLOBS_PER_BLOCK: 12 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index de38514065..38f50f9ac3 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -199,4 +199,4 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 11 # Placeholder BPO3 - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 12 \ No newline at end of file + MAX_BLOBS_PER_BLOCK: 12 From 6ba03b5cfd03b48eb7eab389a51d6449197282b3 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 30 Apr 2025 16:18:55 -0500 Subject: [PATCH 23/25] Add special condition to get_max_blobs_per_block --- configs/mainnet.yaml | 15 +++++------- configs/minimal.yaml | 11 ++++----- specs/fulu/das-core.md | 23 +++++++++++++------ .../fulu/unittests/test_config_invariants.py | 2 +- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 80f80a35e4..f95751121c 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -194,12 +194,9 @@ MAX_BYTES_PER_INCLUSION_LIST: 8192 # --------------------------------------------------------------- BLOB_SCHEDULE: - # Placeholder BPO1 - - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 10 - # Placeholder BPO2 - - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 11 - # Placeholder BPO3 - - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 12 + # Deneb + - EPOCH: 269568 + MAX_BLOBS_PER_BLOCK: 6 + # Electra + - EPOCH: 364032 + MAX_BLOBS_PER_BLOCK: 9 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 38f50f9ac3..464bc3dd5c 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -191,12 +191,9 @@ MAX_BYTES_PER_INCLUSION_LIST: 8192 # --------------------------------------------------------------- BLOB_SCHEDULE: - # Placeholder BPO1 + # Deneb - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 10 - # Placeholder BPO2 + MAX_BLOBS_PER_BLOCK: 6 + # Electra - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 11 - # Placeholder BPO3 - - EPOCH: 18446744073709551615 - MAX_BLOBS_PER_BLOCK: 12 + MAX_BLOBS_PER_BLOCK: 9 diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index f3b3bb8955..c270a7d0e6 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -71,15 +71,14 @@ The following values are (non-configurable) constants used throughout the specif ### Blob schedule -*[New in EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. For epoch values before those defined in the table, the default blob limit is `MAX_BLOBS_PER_BLOCK_ELECTRA`. +*[New in EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. -| Epoch | Max Blobs Per Block | Description | -| ----------------------------- | ------------------- | --------------------------------- | -| `Epoch(18446744073709551615)` | `uint64(10)` | The limit is raised to `10` blobs | -| `Epoch(18446744073709551615)` | `uint64(11)` | The limit is raised to `11` blobs | -| `Epoch(18446744073709551615)` | `uint64(12)` | The limit is raised to `12` blobs | +| Epoch | Max Blobs Per Block | Description | +| --------------- | ------------------- | -------------------------------- | +| `Epoch(269568)` | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` | `uint64(9)` | The limit is raised to `9` blobs | ### Containers @@ -136,10 +135,20 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ```python def get_max_blobs_per_block(epoch: Epoch) -> uint64: + """ + Return the maximum number of blobs that can be included in a block for the current epoch. + """ + # This is a temporary block of code which is only necessary for testing. The testing + # infrastructure uses states where the current epoch is zero but the current version + # is whichever fork is being tested. This does not play well with a getter function + # which uses the current epoch to determine the appropriate return value. + if epoch < FULU_FORK_EPOCH: + return BLOB_SCHEDULE[-1]["MAX_BLOBS_PER_BLOCK"] + 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 + return uint64(0) ``` ### `compute_columns_for_custody_group` diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index 2d9bd4f73e..a1deb9bc52 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -40,6 +40,6 @@ def test_polynomial_commitments_sampling(spec): def test_blob_schedule(spec): for entry in spec.config.BLOB_SCHEDULE: # Check that all epochs are post-Deneb - assert entry["EPOCH"] >= spec.config.FULU_FORK_EPOCH + assert entry["EPOCH"] >= spec.config.DENEB_FORK_EPOCH # Check that all blob counts are less than the limit assert entry["MAX_BLOBS_PER_BLOCK"] <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK From fb9f20f208679b57710fa7734b49df35cfd70884 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 30 Apr 2025 19:38:17 -0500 Subject: [PATCH 24/25] Simplify --- specs/fulu/das-core.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index c270a7d0e6..c96f679e84 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -75,10 +75,10 @@ The following values are (non-configurable) constants used throughout the specif -| Epoch | Max Blobs Per Block | Description | -| --------------- | ------------------- | -------------------------------- | -| `Epoch(269568)` | `uint64(6)` | The limit is set to `6` blobs | -| `Epoch(364032)` | `uint64(9)` | The limit is raised to `9` blobs | +| Epoch | Max Blobs Per Block | Description | +| --------------------------- | ------------------- | -------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | ### Containers @@ -136,19 +136,13 @@ def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence ```python def get_max_blobs_per_block(epoch: Epoch) -> uint64: """ - Return the maximum number of blobs that can be included in a block for the current epoch. + Return the maximum number of blobs that can be included in a block for a given epoch. """ - # This is a temporary block of code which is only necessary for testing. The testing - # infrastructure uses states where the current epoch is zero but the current version - # is whichever fork is being tested. This does not play well with a getter function - # which uses the current epoch to determine the appropriate return value. - if epoch < FULU_FORK_EPOCH: - return BLOB_SCHEDULE[-1]["MAX_BLOBS_PER_BLOCK"] - + assert len(BLOB_SCHEDULE) > 0 for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True): if epoch >= entry["EPOCH"]: return entry["MAX_BLOBS_PER_BLOCK"] - return uint64(0) + return min(entry["MAX_BLOBS_PER_BLOCK"] for entry in BLOB_SCHEDULE) ``` ### `compute_columns_for_custody_group` From 7c5b431f40b71bedc2cc5a07a16d8be45d8abfe1 Mon Sep 17 00:00:00 2001 From: Justin Traglia <95511699+jtraglia@users.noreply.github.com> Date: Thu, 8 May 2025 14:00:24 -0500 Subject: [PATCH 25/25] Use EIP7892 in modified comment Co-authored-by: Preston Van Loon --- specs/fulu/beacon-chain.md | 2 +- specs/fulu/das-core.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/fulu/beacon-chain.md b/specs/fulu/beacon-chain.md index 1f34b40be9..437ba671fc 100644 --- a/specs/fulu/beacon-chain.md +++ b/specs/fulu/beacon-chain.md @@ -38,7 +38,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify commitments are under limit - assert len(body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state)) # [Modified in Fulu:EIP7594] + assert len(body.blob_kzg_commitments) <= get_max_blobs_per_block(get_current_epoch(state)) # [Modified in Fulu:EIP7892] # Verify the execution payload is valid versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] assert execution_engine.verify_and_notify_new_payload( diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md index c96f679e84..f0594d5763 100644 --- a/specs/fulu/das-core.md +++ b/specs/fulu/das-core.md @@ -71,7 +71,7 @@ The following values are (non-configurable) constants used throughout the specif ### Blob schedule -*[New in EIP7594]* This schedule defines the maximum blobs per block limit for a given epoch. +*[New in EIP7892]* This schedule defines the maximum blobs per block limit for a given epoch.