diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index f92a3a548d..27c7f8f817 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -20,6 +20,7 @@ categories: labels: - gloas - eip7732 + - eip7928 # Features - title: EIP-6110 diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index 264fca6374..12f24f45e5 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -33,6 +33,7 @@ jobs: - fulu - gloas - eip7805 + - eip7928 steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 703ee0f2b0..9380156fd4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -68,6 +68,7 @@ jobs: - fulu - gloas - eip7805 + - eip7928 steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.gitignore b/.gitignore index 4a60aeaa14..9bdd397ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ tests/core/pyspec/eth2spec/gloas/ tests/core/pyspec/eth2spec/eip6800/ tests/core/pyspec/eth2spec/eip7441/ tests/core/pyspec/eth2spec/eip7805/ +tests/core/pyspec/eth2spec/eip7928/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index b53e7e89b6..5f36faa5af 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,8 @@ ALL_EXECUTABLE_SPEC_NAMES = \ gloas \ eip6800 \ eip7441 \ - eip7805 + eip7805 \ + eip7928 # A list of fake targets. .PHONY: \ diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 5c2e88c181..32824e4f9c 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -64,6 +64,9 @@ EIP7441_FORK_EPOCH: 18446744073709551615 # EIP7805 EIP7805_FORK_VERSION: 0x0a000000 # temporary stub EIP7805_FORK_EPOCH: 18446744073709551615 +# EIP7928 +EIP7928_FORK_VERSION: 0x0b000000 # temporary stub +EIP7928_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 9ca3bc20fe..d6ddd7addb 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -60,6 +60,9 @@ EIP7441_FORK_EPOCH: 18446744073709551615 # [customized] EIP7805 EIP7805_FORK_VERSION: 0x0a000001 EIP7805_FORK_EPOCH: 18446744073709551615 +# [customized] EIP7928 +EIP7928_FORK_VERSION: 0x0b000001 +EIP7928_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- diff --git a/pysetup/constants.py b/pysetup/constants.py index 34196021a1..15ebd15e45 100644 --- a/pysetup/constants.py +++ b/pysetup/constants.py @@ -10,6 +10,7 @@ EIP6800 = "eip6800" EIP7441 = "eip7441" EIP7805 = "eip7805" +EIP7928 = "eip7928" # The helper functions that are used when defining constants diff --git a/pysetup/md_doc_paths.py b/pysetup/md_doc_paths.py index 303138eb75..a1e85bbaf5 100644 --- a/pysetup/md_doc_paths.py +++ b/pysetup/md_doc_paths.py @@ -8,6 +8,7 @@ EIP6800, EIP7441, EIP7805, + EIP7928, ELECTRA, FULU, GLOAS, @@ -26,6 +27,7 @@ EIP6800: DENEB, EIP7441: CAPELLA, EIP7805: FULU, + EIP7928: FULU, } ALL_FORKS = list(PREVIOUS_FORK_OF.keys()) diff --git a/pysetup/spec_builders/__init__.py b/pysetup/spec_builders/__init__.py index bfdaab177a..5306322fdb 100644 --- a/pysetup/spec_builders/__init__.py +++ b/pysetup/spec_builders/__init__.py @@ -5,6 +5,7 @@ from .eip6800 import EIP6800SpecBuilder from .eip7441 import EIP7441SpecBuilder from .eip7805 import EIP7805SpecBuilder +from .eip7928 import EIP7928SpecBuilder from .electra import ElectraSpecBuilder from .fulu import FuluSpecBuilder from .gloas import GloasSpecBuilder @@ -24,5 +25,6 @@ EIP6800SpecBuilder, EIP7441SpecBuilder, EIP7805SpecBuilder, + EIP7928SpecBuilder, ) } diff --git a/pysetup/spec_builders/eip7928.py b/pysetup/spec_builders/eip7928.py new file mode 100644 index 0000000000..07c109c2b5 --- /dev/null +++ b/pysetup/spec_builders/eip7928.py @@ -0,0 +1,12 @@ +from ..constants import EIP7928 +from .base import BaseSpecBuilder + + +class EIP7928SpecBuilder(BaseSpecBuilder): + fork: str = EIP7928 + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.fulu import {preset_name} as fulu +""" diff --git a/specs/_features/eip7928/beacon-chain.md b/specs/_features/eip7928/beacon-chain.md new file mode 100644 index 0000000000..0ed215dcf1 --- /dev/null +++ b/specs/_features/eip7928/beacon-chain.md @@ -0,0 +1,147 @@ +# EIP-7928 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Extended Containers](#extended-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`NewPayloadRequest`](#newpayloadrequest) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + + + +## Introduction + +*Note*: This specification is built upon [Fulu](../../fulu/beacon-chain.md) and +is under active development. + +## Custom types + +| Name | SSZ equivalent | Description | +| ----------------- | ------------------------------------- | ----------------------------- | +| `BlockAccessList` | `ByteList[MAX_BYTES_PER_TRANSACTION]` | RLP encoded block access list | + +## Extended Containers + +### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + blob_gas_used: uint64 + excess_blob_gas: uint64 + # [New in EIP7928] + block_access_list: BlockAccessList +``` + +### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions_root: Root + withdrawals_root: Root + blob_gas_used: uint64 + excess_blob_gas: uint64 + # [New in EIP7928] + block_access_list_root: Root +``` + +### `NewPayloadRequest` + +*Note*: The `NewPayloadRequest` is unchanged. The `block_access_list` is +included in the `execution_payload` field. + +## Beacon chain state transition function + +### Block processing + +#### Execution payload + +##### Modified `process_execution_payload` + +```python +def process_execution_payload( + state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine +) -> None: + payload = body.execution_payload + + # Verify consistency of the parent hash with respect to the previous execution payload header + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_time_at_slot(state, state.slot) + # Verify commitments are under limit + assert ( + len(body.blob_kzg_commitments) + <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block + ) + # 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( + NewPayloadRequest( + execution_payload=payload, + versioned_hashes=versioned_hashes, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests=body.execution_requests, + ) + ) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), + blob_gas_used=payload.blob_gas_used, + excess_blob_gas=payload.excess_blob_gas, + # [New in EIP7928] + block_access_list_root=hash_tree_root(payload.block_access_list), + ) +``` diff --git a/specs/_features/eip7928/fork.md b/specs/_features/eip7928/fork.md new file mode 100644 index 0000000000..93d741a266 --- /dev/null +++ b/specs/_features/eip7928/fork.md @@ -0,0 +1,111 @@ +# EIP-7928 -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to EIP-7928](#fork-to-eip-7928) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the EIP-7928 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | ------------------------------------- | +| `EIP7928_FORK_VERSION` | `Version('0x0b000000')` | +| `EIP7928_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Fork to EIP-7928 + +### Fork trigger + +The fork is triggered at epoch `EIP7928_FORK_EPOCH`. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == EIP7928_FORK_EPOCH`, an irregular state +change is made to upgrade to EIP-7928. + +```python +def upgrade_to_eip7928(pre: fulu.BeaconState) -> BeaconState: + epoch = get_current_epoch(pre) + latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=pre.latest_execution_payload_header.parent_hash, + fee_recipient=pre.latest_execution_payload_header.fee_recipient, + state_root=pre.latest_execution_payload_header.state_root, + receipts_root=pre.latest_execution_payload_header.receipts_root, + logs_bloom=pre.latest_execution_payload_header.logs_bloom, + prev_randao=pre.latest_execution_payload_header.prev_randao, + block_number=pre.latest_execution_payload_header.block_number, + gas_limit=pre.latest_execution_payload_header.gas_limit, + gas_used=pre.latest_execution_payload_header.gas_used, + timestamp=pre.latest_execution_payload_header.timestamp, + extra_data=pre.latest_execution_payload_header.extra_data, + base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas, + block_hash=pre.latest_execution_payload_header.block_hash, + transactions_root=pre.latest_execution_payload_header.transactions_root, + withdrawals_root=pre.latest_execution_payload_header.withdrawals_root, + blob_gas_used=pre.latest_execution_payload_header.blob_gas_used, + excess_blob_gas=pre.latest_execution_payload_header.excess_blob_gas, + # [New in EIP7928] + block_access_list_root=Root(), + ) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in EIP7928] + current_version=EIP7928_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + proposer_lookahead=pre.proposer_lookahead, + ) + + return post +``` diff --git a/specs/_features/eip7928/p2p-interface.md b/specs/_features/eip7928/p2p-interface.md new file mode 100644 index 0000000000..6f9404446f --- /dev/null +++ b/specs/_features/eip7928/p2p-interface.md @@ -0,0 +1,40 @@ +# EIP-7928 -- Networking + +This document contains the consensus-layer networking specification for +EIP-7928. + + + +- [Modifications in EIP-7928](#modifications-in-eip-7928) + - [Helper functions](#helper-functions) + - [Modified `compute_fork_version`](#modified-compute_fork_version) + + + +## Modifications in EIP-7928 + +### Helper functions + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= EIP7928_FORK_EPOCH: + return EIP7928_FORK_VERSION + if epoch >= FULU_FORK_EPOCH: + return FULU_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 4c5dd6372d..59816b88d0 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -20,6 +20,7 @@ DENEB, EIP7441, EIP7805, + EIP7928, ELECTRA, FULU, GLOAS, @@ -654,6 +655,7 @@ def wrapper(*args, spec: Spec, **kw): with_gloas_and_later = with_all_phases_from(GLOAS, all_phases=ALLOWED_TEST_RUNNER_FORKS) with_eip7441_and_later = with_all_phases_from(EIP7441, all_phases=ALLOWED_TEST_RUNNER_FORKS) with_eip7805_and_later = with_all_phases_from(EIP7805, all_phases=ALLOWED_TEST_RUNNER_FORKS) +with_eip7928_and_later = with_all_phases_from(EIP7928, all_phases=ALLOWED_TEST_RUNNER_FORKS) class quoted_str(str): diff --git a/tests/core/pyspec/eth2spec/test/eip7928/__init__.py b/tests/core/pyspec/eth2spec/test/eip7928/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7928/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/eip7928/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7928/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/eip7928/block_processing/test_process_execution_payload.py new file mode 100644 index 0000000000..bb7c391eeb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7928/block_processing/test_process_execution_payload.py @@ -0,0 +1,37 @@ +""" +Tests for EIP-7928 block access list field. +""" + +from eth2spec.test.context import ( + spec_state_test, + with_eip7928_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, +) +from eth2spec.test.helpers.state import next_slot + + +@with_eip7928_and_later +@spec_state_test +def test_execution_payload_with_block_access_list(spec, state): + """Test that execution payload correctly includes block access list field.""" + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + # Verify field exists and can be set + assert hasattr(execution_payload, "block_access_list") + execution_payload.block_access_list = spec.ByteList[spec.MAX_BYTES_PER_TRANSACTION]( + b"\xc0" * 100 + ) + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Process payload + body = spec.BeaconBlockBody(execution_payload=execution_payload) + spec.process_execution_payload(state, body, spec.NoopExecutionEngine()) + + # Verify header contains the root + assert state.latest_execution_payload_header.block_access_list_root == spec.hash_tree_root( + execution_payload.block_access_list + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index e669c85df3..64d1ebe98c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -17,6 +17,7 @@ # Experimental phases (not included in default "ALL_PHASES"): EIP7441 = SpecForkName("eip7441") EIP7805 = SpecForkName("eip7805") +EIP7928 = SpecForkName("eip7928") # # SpecFork settings @@ -35,6 +36,7 @@ GLOAS, # Experimental patches EIP7805, + EIP7928, ) # The forks that have light client specs LIGHT_CLIENT_TESTING_FORKS = [item for item in MAINNET_FORKS if item != PHASE0] @@ -57,6 +59,7 @@ # Experimental patches EIP7441: CAPELLA, EIP7805: FULU, + EIP7928: FULU, } # For fork transition tests diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index d8becc7883..e47b1e814b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -9,6 +9,7 @@ from eth2spec.test.helpers.forks import ( is_post_capella, is_post_deneb, + is_post_eip7928, is_post_electra, is_post_gloas, ) @@ -49,6 +50,10 @@ def get_execution_payload_header(spec, state, execution_payload): if is_post_deneb(spec): payload_header.blob_gas_used = execution_payload.blob_gas_used payload_header.excess_blob_gas = execution_payload.excess_blob_gas + if is_post_eip7928(spec): + payload_header.block_access_list_root = spec.hash_tree_root( + execution_payload.block_access_list + ) return payload_header @@ -337,6 +342,9 @@ def build_empty_execution_payload(spec, state, randao_mix=None): if is_post_deneb(spec): payload.blob_gas_used = 0 payload.excess_blob_gas = 0 + if is_post_eip7928(spec): + # Add empty block access list for EIP7928 + payload.block_access_list = spec.ByteList[spec.MAX_BYTES_PER_TRANSACTION]() payload.block_hash = compute_el_block_hash(spec, payload, state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/forks.py b/tests/core/pyspec/eth2spec/test/helpers/forks.py index 89330e9494..34d0b729be 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/forks.py +++ b/tests/core/pyspec/eth2spec/test/helpers/forks.py @@ -5,6 +5,7 @@ DENEB, EIP7441, EIP7805, + EIP7928, ELECTRA, FULU, GLOAS, @@ -65,6 +66,10 @@ def is_post_eip7805(spec): return is_post_fork(spec.fork, EIP7805) +def is_post_eip7928(spec): + return is_post_fork(spec.fork, EIP7928) + + def get_spec_for_fork_version(spec, fork_version, phases): if phases is None: return spec