Skip to content

Commit a93a288

Browse files
committed
feat(specs): Implement EIP-7928: Block-Level Access Lists
fix(tests): Fix Amsterdam filling after rebase fix(specs): Fix issues with new ruff + mypy rules after rebase - bal -> block_access_list; re-add custom rlp encoding for block access list - bytes to uint - move away from method-style - Update EIP-7928 implementation: system contracts at index 0, migrate to RLP - System contracts (parent hash, beacon root) now use block_access_index 0 - Transactions use block_access_index 1 to len(transactions) - Post-execution changes use block_access_index len(transactions) + 1 - Migrated from SSZ to RLP encoding as per updated EIP-7928 spec - Updated all tests to match new API and structure - Replaced tx_index with block_access_index throughout codebase - add system contract logic - add markdown docstrings - update BAL format; address comments - ssz encoding and bal validation - six ssz types - bal tests - balspecs fix: do not track setting empty code to a new account (#19) fix: track implicit SLOAD within SSTORE for OOG cases (#18) refactor: Put back explicit acct tracking outside 7702 delegation path (#17) fix non-tracked 7702 authority for invalid delegations (#16) * fix non-tracked 7702 authority for invalid delegations * fix: lint issues * fix: track delegation target when loaded as call target * fix: track delegation target when loaded as call target from call opcodes * chore: fix issues with documentation generation Fix self-destruct cases with pre-execution balance cache / tracking * fix self-destruct implementation * fix self-destruct tracking balance * fix it in the bal finalization by filtering * add balance reset and fix tests * simplify pre-balance tracking not using snapshots fix duplicated code entries for in transaction self destruct fix self destruct in same transaction bug fix call/delagate call tracking bug fix zero-value transfer tracking (#6) * fix zero-value transfer tracking * fix reverted frame tracking * rename variables * fix missing addresses bug * fix: docs run & move imports to top of file refactor: move rlp_utils to block_access_lists; bal -> block_access_lists Some remaining fixes due to large refactor in `forks/osaka`: - Move BALs from amsterdam -> forks/amsterdam - rename: build -> build_block_access_list - fix docc issues move state change tracker to State correct system contract addresses Fixes to communicate with BALs EEST branch: - fix(bal): Initialize the state tracker before system contract calls - We were missing system contract calls to beacon roots and history contracts. This change initializes the state tracker before system contract calls and passes the tracker to these calls if post-Amsterdam. - fix(docs): Fix issues with toxenvs: lint, doc, json_infra - fix(t8n): Only initialize the bal_change_tracker for amsterdam - feat(fork criteria): Index upcoming forks for better ordering / fix issues - chore(forks): Fix issues from lint after rebase with Osaka latest - fix(setuptools): Update packages to include amsterdam - chore(lint): Fix 'tox -e static' issues - Fix bug in tracker Manually cherry-picked from e72991b Author: nerolation - chore(tests): Attempt to resolve issues with CI tests - chore(lint): fix issues from running ``tox -e static`` locally - refactor(bal): Send BAL as a list over t8n tool - fix(amsterdam): Add change tracker to state test in t8n - chore(lint,tests): Fix tests after moving bal from osaka -> amsterdam - chore(forks): Move bals from Osaka to Amsterdam - chore(lint): Fix lint issues - refactor(bal): Send the full bal object and bal_hash over t8n - If we send the full object over JSON, we can model_validate() on ESST. - If we send the hash, once we fill the pydantic model, we can get the rlp and the hash and validate that our objects match while only really validating the parts of the BAL we are interested in for each test. - chore: point to working eest branch - chore(bals): Remove unused SSZ utils.py The SSZ implementation is no longer needed as we are now using RLP - refactor(bals): Clean up BAL module types and imports - Bytes -> Bytes32 type for storage slots - Remove unused imports / fix imports / fix linting - Update function signatures to match tracker - fix(bals-tx-index): Track bal indexes in t8n Keep track of BAL index state in t8n
1 parent 849d892 commit a93a288

57 files changed

Lines changed: 9773 additions & 1614 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/configs/feature.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ benchmark_fast:
2323

2424
bal:
2525
evm-type: develop
26-
fill-params: --fork=Amsterdam ./tests/amsterdam/eip7928_block_level_access_lists
26+
# TODO: Turn on block rlp limit tests after making filling them more flexible.
27+
fill-params: --fork=Amsterdam -k "not eip7934"
2728
feature_only: true

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Test fixtures for use by clients are available for each release on the [Github r
4848
### 📋 Misc
4949

5050
- 🐞 WELDed the EEST tox environments relevant to producing documentation into EELS, and added a tool to cleanly add codespell whitelist entries. ([#1695](https://github.com/ethereum/execution-specs/pull/1659)).
51+
- 🐞 Fix duplicate storage write issues for block access lists EIP-7928 implementation ([#1743](https://github.com/ethereum/execution-specs/pull/1743)).
5152

5253
### 🧪 Test Cases
5354

packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/ruleset.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,6 @@ def get_blob_schedule_entries(fork: Fork) -> Dict[str, int]:
296296
"HIVE_CANCUN_TIMESTAMP": 0,
297297
"HIVE_PRAGUE_TIMESTAMP": 0,
298298
"HIVE_OSAKA_TIMESTAMP": 0,
299-
**get_blob_schedule_entries(Osaka),
300299
},
301300
PragueToOsakaAtTime15k: {
302301
"HIVE_FORK_HOMESTEAD": 0,
@@ -314,7 +313,6 @@ def get_blob_schedule_entries(fork: Fork) -> Dict[str, int]:
314313
"HIVE_CANCUN_TIMESTAMP": 0,
315314
"HIVE_PRAGUE_TIMESTAMP": 0,
316315
"HIVE_OSAKA_TIMESTAMP": 15000,
317-
**get_blob_schedule_entries(Osaka),
318316
},
319317
BPO1: {
320318
"HIVE_FORK_HOMESTEAD": 0,
@@ -496,11 +494,10 @@ def get_blob_schedule_entries(fork: Fork) -> Dict[str, int]:
496494
"HIVE_CANCUN_TIMESTAMP": 0,
497495
"HIVE_PRAGUE_TIMESTAMP": 0,
498496
"HIVE_OSAKA_TIMESTAMP": 0,
499-
"HIVE_BPO1_TIMESTAMP": 0,
500-
"HIVE_BPO2_TIMESTAMP": 0,
501-
"HIVE_BPO3_TIMESTAMP": 0,
502-
"HIVE_BPO4_TIMESTAMP": 0,
497+
# TODO: While we are still reworking BPO interaction with T8N,
498+
# turn off BPO timestamps for now.
499+
# "HIVE_BPO1_TIMESTAMP": 0,
500+
# "HIVE_BPO2_TIMESTAMP": 0,
503501
"HIVE_AMSTERDAM_TIMESTAMP": 0,
504-
**get_blob_schedule_entries(Amsterdam),
505502
},
506503
}

packages/testing/src/execution_testing/client_clis/clis/erigon.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class ErigonExceptionMapper(ExceptionMapper):
5656
BlockException.INVALID_LOG_BLOOM: "invalid bloom",
5757
}
5858
mapping_regex = {
59+
BlockException.INVALID_BLOCK_ACCESS_LIST: r"invalid block access list|block access list mismatch",
5960
TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM: (
6061
r"invalid block, txnIdx=\d+,.*gas limit too high"
6162
),

packages/testing/src/execution_testing/fixtures/blockchain.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ class FixtureHeader(CamelModel):
199199
requests_hash: (
200200
Annotated[Hash, HeaderForkRequirement("requests")] | None
201201
) = Field(None)
202+
block_access_list_hash: (
203+
Annotated[Hash, HeaderForkRequirement("bal_hash")] | None
204+
) = Field(None, alias="blockAccessListHash")
202205

203206
fork: Fork | None = Field(None, exclude=True)
204207

@@ -283,6 +286,11 @@ def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self:
283286
"requests_hash": Requests()
284287
if fork.header_requests_required(block_number=0, timestamp=0)
285288
else None,
289+
"block_access_list_hash": (
290+
BlockAccessList().rlp_hash
291+
if fork.header_bal_hash_required(block_number=0, timestamp=0)
292+
else None
293+
),
286294
"fork": fork,
287295
}
288296
return cls(**environment_values, **extras)
@@ -408,6 +416,14 @@ def from_fixture_header(
408416
"Invalid header for engine_newPayload"
409417
)
410418

419+
if fork.engine_execution_payload_block_access_list(
420+
block_number=header.number, timestamp=header.timestamp
421+
):
422+
if block_access_list is None:
423+
raise ValueError(
424+
f"`block_access_list` is required in engine `ExecutionPayload` for >={fork}."
425+
)
426+
411427
execution_payload = FixtureExecutionPayload.from_fixture_header(
412428
header=header,
413429
transactions=transactions,

packages/testing/src/execution_testing/forks/base_fork.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,14 @@ def header_requests_required(
345345
"""Return true if the header must contain beacon chain requests."""
346346
pass
347347

348+
@classmethod
349+
@abstractmethod
350+
def header_bal_hash_required(
351+
cls, *, block_number: int = 0, timestamp: int = 0
352+
) -> bool:
353+
"""Return true if the header must contain block access list hash."""
354+
pass
355+
348356
# Gas related abstract methods
349357

350358
@classmethod
@@ -710,6 +718,17 @@ def engine_new_payload_target_blobs_per_block(
710718
"""
711719
pass
712720

721+
@classmethod
722+
@abstractmethod
723+
def engine_execution_payload_block_access_list(
724+
cls, *, block_number: int = 0, timestamp: int = 0
725+
) -> bool:
726+
"""
727+
Return `True` if the engine api version requires execution payload to
728+
include a `block_access_list`.
729+
"""
730+
pass
731+
713732
@classmethod
714733
@abstractmethod
715734
def engine_payload_attribute_target_blobs_per_block(

packages/testing/src/execution_testing/forks/forks/forks.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,14 @@ def header_requests_required(
443443
del block_number, timestamp
444444
return False
445445

446+
@classmethod
447+
def header_bal_hash_required(
448+
cls, *, block_number: int = 0, timestamp: int = 0
449+
) -> bool:
450+
"""At genesis, header must not contain block access list hash."""
451+
del block_number, timestamp
452+
return False
453+
446454
@classmethod
447455
def engine_new_payload_version(
448456
cls, *, block_number: int = 0, timestamp: int = 0
@@ -483,6 +491,14 @@ def engine_new_payload_requests(
483491
del block_number, timestamp
484492
return False
485493

494+
@classmethod
495+
def engine_execution_payload_block_access_list(
496+
cls, *, block_number: int = 0, timestamp: int = 0
497+
) -> bool:
498+
"""At genesis, payloads do not have block access list."""
499+
del block_number, timestamp
500+
return False
501+
486502
@classmethod
487503
def engine_new_payload_target_blobs_per_block(
488504
cls,
@@ -2462,6 +2478,16 @@ class BPO5(BPO4, bpo_fork=True):
24622478
class Amsterdam(Osaka):
24632479
"""Amsterdam fork."""
24642480

2481+
@classmethod
2482+
def header_bal_hash_required(
2483+
cls, *, block_number: int = 0, timestamp: int = 0
2484+
) -> bool:
2485+
"""
2486+
From Amsterdam, header must contain block access list hash (EIP-7928).
2487+
"""
2488+
del block_number, timestamp
2489+
return True
2490+
24652491
@classmethod
24662492
def is_deployed(cls) -> bool:
24672493
"""Return True if this fork is deployed."""
@@ -2475,6 +2501,17 @@ def engine_new_payload_version(
24752501
del block_number, timestamp
24762502
return 5
24772503

2504+
@classmethod
2505+
def engine_execution_payload_block_access_list(
2506+
cls, *, block_number: int = 0, timestamp: int = 0
2507+
) -> bool:
2508+
"""
2509+
From Amsterdam, engine execution payload includes `block_access_list`
2510+
as a parameter.
2511+
"""
2512+
del block_number, timestamp
2513+
return True
2514+
24782515

24792516
class EOFv1(Prague, solc_name="cancun"):
24802517
"""EOF fork."""

packages/testing/src/execution_testing/specs/blockchain.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,11 +551,16 @@ def make_genesis(
551551
state_root = pre_alloc.state_root()
552552
genesis = FixtureHeader.genesis(self.fork, env, state_root)
553553

554+
genesis_bal = None
555+
if self.fork.header_bal_hash_required(block_number=0, timestamp=0):
556+
genesis_bal = BlockAccessList()
557+
554558
return (
555559
pre_alloc,
556560
FixtureBlockBase(
557561
header=genesis,
558562
withdrawals=None if env.withdrawals is None else [],
563+
block_access_list=genesis_bal,
559564
).with_rlp(txs=[]),
560565
)
561566

@@ -687,6 +692,24 @@ def generate_block_data(
687692
)
688693
requests_list = block.requests
689694

695+
if self.fork.header_bal_hash_required(
696+
block_number=header.number, timestamp=header.timestamp
697+
):
698+
assert (
699+
transition_tool_output.result.block_access_list is not None
700+
), (
701+
"Block access list is required for this block but was not provided "
702+
"by the transition tool"
703+
)
704+
705+
rlp = transition_tool_output.result.block_access_list.rlp
706+
computed_bal_hash = Hash(rlp.keccak256())
707+
assert computed_bal_hash == header.block_access_list_hash, (
708+
"Block access list hash in header does not match the "
709+
f"computed hash from BAL: {header.block_access_list_hash} "
710+
f"!= {computed_bal_hash}"
711+
)
712+
690713
if block.rlp_modifier is not None:
691714
# Modify any parameter specified in the `rlp_modifier` after
692715
# transition tool processing.
@@ -695,6 +718,29 @@ def generate_block_data(
695718
self.fork
696719
) # Deleted during `apply` because `exclude=True`
697720

721+
# Process block access list - apply transformer if present for invalid
722+
# tests
723+
t8n_bal = transition_tool_output.result.block_access_list
724+
bal = t8n_bal
725+
726+
# Always validate BAL structural integrity (ordering, duplicates) if present
727+
if t8n_bal is not None:
728+
t8n_bal.validate_structure()
729+
730+
# If expected BAL is defined, verify against it
731+
if (
732+
block.expected_block_access_list is not None
733+
and t8n_bal is not None
734+
):
735+
block.expected_block_access_list.verify_against(t8n_bal)
736+
737+
bal = block.expected_block_access_list.modify_if_invalid_test(
738+
t8n_bal
739+
)
740+
if bal != t8n_bal:
741+
# If the BAL was modified, update the header hash
742+
header.block_access_list_hash = Hash(bal.rlp.keccak256())
743+
698744
built_block = BuiltBlock(
699745
header=header,
700746
alloc=transition_tool_output.alloc,
@@ -708,7 +754,7 @@ def generate_block_data(
708754
expected_exception=block.exception,
709755
engine_api_error_code=block.engine_api_error_code,
710756
fork=self.fork,
711-
block_access_list=None,
757+
block_access_list=bal,
712758
)
713759

714760
try:

packages/testing/src/execution_testing/test_types/block_access_list/account_absent_values.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ class BalAccountAbsentValues(CamelModel):
5252
absent_values = BalAccountAbsentValues(
5353
nonce_changes=[
5454
# Forbid exact nonce change at this tx
55-
BalNonceChange(tx_index=1, post_nonce=5),
55+
BalNonceChange(block_access_index=1, post_nonce=5),
5656
],
5757
storage_changes=[
5858
BalStorageSlot(
5959
slot=0x42,
6060
slot_changes=[
6161
# Forbid exact storage change at this slot and tx
62-
BalStorageChange(tx_index=2, post_value=0x99)
62+
BalStorageChange(block_access_index=2, post_value=0x99)
6363
],
6464
)
6565
],
@@ -173,22 +173,23 @@ def validate_against(self, account: BalAccountChange) -> None:
173173
self._validate_forbidden_changes(
174174
account.nonce_changes,
175175
self.nonce_changes,
176-
lambda a, f: a.tx_index == f.tx_index
176+
lambda a, f: a.block_access_index == f.block_access_index
177177
and a.post_nonce == f.post_nonce,
178-
lambda a: f"Unexpected nonce change found at tx {a.tx_index}",
178+
lambda a: f"Unexpected nonce change found at tx {a.block_access_index}",
179179
)
180180
self._validate_forbidden_changes(
181181
account.balance_changes,
182182
self.balance_changes,
183-
lambda a, f: a.tx_index == f.tx_index
183+
lambda a, f: a.block_access_index == f.block_access_index
184184
and a.post_balance == f.post_balance,
185-
lambda a: f"Unexpected balance change found at tx {a.tx_index}",
185+
lambda a: f"Unexpected balance change found at tx {a.block_access_index}",
186186
)
187187
self._validate_forbidden_changes(
188188
account.code_changes,
189189
self.code_changes,
190-
lambda a, f: a.tx_index == f.tx_index and a.new_code == f.new_code,
191-
lambda a: f"Unexpected code change found at tx {a.tx_index}",
190+
lambda a, f: a.block_access_index == f.block_access_index
191+
and a.new_code == f.new_code,
192+
lambda a: f"Unexpected code change found at tx {a.block_access_index}",
192193
)
193194

194195
for forbidden_storage_slot in self.storage_changes:
@@ -199,11 +200,11 @@ def validate_against(self, account: BalAccountChange) -> None:
199200
actual_storage_slot.slot_changes,
200201
forbidden_storage_slot.slot_changes,
201202
lambda a, f: (
202-
a.tx_index == f.tx_index
203+
a.block_access_index == f.block_access_index
203204
and a.post_value == f.post_value
204205
),
205206
lambda a, slot=slot_id: (
206-
f"Unexpected storage change found at slot {slot} in tx {a.tx_index}"
207+
f"Unexpected storage change found at slot {slot} in tx {a.block_access_index}"
207208
),
208209
)
209210

0 commit comments

Comments
 (0)