Skip to content

Commit 5d1606d

Browse files
committed
fix(test-specs): validate t8n BAL independent of expectation existence
- Validate static checks on the t8n BAL if it exists - IF the expectation also exists, validate against the expectation Keep these checks separate as this helps validation now that we fill for all tests, regardless if they have an expectation or not.
1 parent 36443d2 commit 5d1606d

File tree

6 files changed

+457
-337
lines changed

6 files changed

+457
-337
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Test fixtures for use by clients are available for each release on the [Github r
2525
- 🐞 Fix BALs opcode OOG test vectors by updating the Amsterdam commit hash in specs and validating appropriately on the testing side ([#2293](https://github.com/ethereum/execution-spec-tests/pull/2293)).
2626
- ✨ Fix test vector for BALs SSTORE with OOG by pointing to updated specs; add new boundary conditions cases for SSTORE w/ OOG ([#2297](https://github.com/ethereum/execution-spec-tests/pull/2297)).
2727
- ✨ Expand cases to test *CALL opcodes causing OOG ([#1703](https://github.com/ethereum/execution-specs/pull/1703)).
28+
- 🐞 Fix `BlockAccessList` validation for T8N to run independent of whether a `BlockAccessListExpectation` is defined for the test ([#1742](https://github.com/ethereum/execution-specs/pull/1742)).
2829

2930
## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09
3031

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,12 @@ def generate_block_data(
715715
# tests
716716
t8n_bal = transition_tool_output.result.block_access_list
717717
bal = t8n_bal
718+
719+
# Always validate BAL structural integrity (ordering, duplicates) if present
720+
if t8n_bal is not None:
721+
t8n_bal.validate_structure()
722+
723+
# If expected BAL is defined, verify against it
718724
if (
719725
block.expected_block_access_list is not None
720726
and t8n_bal is not None

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

Lines changed: 2 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
BalCodeChange,
1919
BalNonceChange,
2020
BalStorageSlot,
21-
BlockAccessListChangeLists,
2221
)
2322
from .exceptions import BlockAccessListValidationError
2423
from .t8n import BlockAccessList
@@ -179,9 +178,8 @@ def verify_against(self, actual_bal: "BlockAccessList") -> None:
179178
Verify that the actual BAL from the client matches this expected BAL.
180179
181180
Validation steps:
182-
1. Validate actual BAL conforms to EIP-7928 ordering requirements
183-
2. Verify address expectations - presence or explicit absence
184-
3. Verify expected changes within accounts match actual changes
181+
1. Verify address expectations - presence or explicit absence
182+
2. Verify expected changes within accounts match actual changes
185183
186184
Args:
187185
actual_bal: The BlockAccessList model from the client
@@ -190,9 +188,6 @@ def verify_against(self, actual_bal: "BlockAccessList") -> None:
190188
BlockAccessListValidationError: If verification fails
191189
192190
"""
193-
# validate the actual BAL structure follows EIP-7928 ordering
194-
self._validate_bal_ordering(actual_bal)
195-
196191
actual_accounts_by_addr = {acc.address: acc for acc in actual_bal.root}
197192
for address, expectation in self.account_expectations.items():
198193
if expectation is None:
@@ -236,111 +231,6 @@ def verify_against(self, actual_bal: "BlockAccessList") -> None:
236231
f"Account {address}: {str(e)}"
237232
) from e
238233

239-
@staticmethod
240-
def _validate_bal_ordering(bal: "BlockAccessList") -> None:
241-
"""
242-
Validate BAL ordering follows EIP-7928 requirements.
243-
244-
Args:
245-
bal: The BlockAccessList to validate
246-
247-
Raises:
248-
BlockAccessListValidationError: If ordering is invalid
249-
250-
"""
251-
# Check address ordering (ascending)
252-
for i in range(1, len(bal.root)):
253-
if bal.root[i - 1].address >= bal.root[i].address:
254-
raise BlockAccessListValidationError(
255-
f"BAL addresses are not in lexicographic order: "
256-
f"{bal.root[i - 1].address} >= {bal.root[i].address}"
257-
)
258-
259-
# Check transaction index ordering and uniqueness within accounts
260-
for account in bal.root:
261-
changes_to_check: List[tuple[str, BlockAccessListChangeLists]] = [
262-
("nonce_changes", account.nonce_changes),
263-
("balance_changes", account.balance_changes),
264-
("code_changes", account.code_changes),
265-
]
266-
267-
for field_name, change_list in changes_to_check:
268-
if not change_list:
269-
continue
270-
271-
tx_indices = [c.tx_index for c in change_list]
272-
273-
# Check both ordering and duplicates
274-
if tx_indices != sorted(tx_indices):
275-
raise BlockAccessListValidationError(
276-
f"Transaction indices not in ascending order in {field_name} of account "
277-
f"{account.address}. Got: {tx_indices}, Expected: {sorted(tx_indices)}"
278-
)
279-
280-
if len(tx_indices) != len(set(tx_indices)):
281-
duplicates = sorted(
282-
{
283-
idx
284-
for idx in tx_indices
285-
if tx_indices.count(idx) > 1
286-
}
287-
)
288-
raise BlockAccessListValidationError(
289-
f"Duplicate transaction indices in {field_name} of account "
290-
f"{account.address}. Duplicates: {duplicates}"
291-
)
292-
293-
# Check storage slot ordering
294-
for i in range(1, len(account.storage_changes)):
295-
if (
296-
account.storage_changes[i - 1].slot
297-
>= account.storage_changes[i].slot
298-
):
299-
raise BlockAccessListValidationError(
300-
f"Storage slots not in ascending order in account "
301-
f"{account.address}: {account.storage_changes[i - 1].slot} >= "
302-
f"{account.storage_changes[i].slot}"
303-
)
304-
305-
# Check transaction index ordering and uniqueness within storage
306-
# slots
307-
for storage_slot in account.storage_changes:
308-
if not storage_slot.slot_changes:
309-
continue
310-
311-
tx_indices = [c.tx_index for c in storage_slot.slot_changes]
312-
313-
# Check both ordering and duplicates
314-
if tx_indices != sorted(tx_indices):
315-
raise BlockAccessListValidationError(
316-
f"Transaction indices not in ascending order in storage slot "
317-
f"{storage_slot.slot} of account {account.address}. "
318-
f"Got: {tx_indices}, Expected: {sorted(tx_indices)}"
319-
)
320-
321-
if len(tx_indices) != len(set(tx_indices)):
322-
duplicates = sorted(
323-
{
324-
idx
325-
for idx in tx_indices
326-
if tx_indices.count(idx) > 1
327-
}
328-
)
329-
raise BlockAccessListValidationError(
330-
f"Duplicate transaction indices in storage slot "
331-
f"{storage_slot.slot} of account {account.address}. "
332-
f"Duplicates: {duplicates}"
333-
)
334-
335-
# Check storage reads ordering
336-
for i in range(1, len(account.storage_reads)):
337-
if account.storage_reads[i - 1] >= account.storage_reads[i]:
338-
raise BlockAccessListValidationError(
339-
f"Storage reads not in ascending order in account "
340-
f"{account.address}: {account.storage_reads[i - 1]} >= "
341-
f"{account.storage_reads[i]}"
342-
)
343-
344234
@staticmethod
345235
def _compare_account_expectations(
346236
expected: BalAccountExpectation, actual: BalAccountChange

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
)
1313

1414
from .account_changes import BalAccountChange
15+
from .exceptions import BlockAccessListValidationError
1516

1617

1718
class BlockAccessList(EthereumTestRootModel[List[BalAccountChange]]):
@@ -49,3 +50,108 @@ def rlp(self) -> Bytes:
4950
def rlp_hash(self) -> Bytes:
5051
"""Return the hash of the RLP encoded block access list."""
5152
return self.rlp.keccak256()
53+
54+
def validate_structure(self) -> None:
55+
"""
56+
Validate BAL structure follows EIP-7928 requirements.
57+
58+
Checks:
59+
- Addresses are in lexicographic (ascending) order
60+
- Transaction indices are sorted and unique within each change list
61+
- Storage slots are in ascending order
62+
- Storage reads are in ascending order
63+
64+
Raises:
65+
BlockAccessListValidationError: If validation fails
66+
"""
67+
# Check address ordering (ascending)
68+
for i in range(1, len(self.root)):
69+
if self.root[i - 1].address >= self.root[i].address:
70+
raise BlockAccessListValidationError(
71+
f"BAL addresses are not in lexicographic order: "
72+
f"{self.root[i - 1].address} >= {self.root[i].address}"
73+
)
74+
75+
# Check transaction index ordering and uniqueness within accounts
76+
for account in self.root:
77+
changes_to_check: List[tuple[str, List[Any]]] = [
78+
("nonce_changes", account.nonce_changes),
79+
("balance_changes", account.balance_changes),
80+
("code_changes", account.code_changes),
81+
]
82+
83+
for field_name, change_list in changes_to_check:
84+
if not change_list:
85+
continue
86+
87+
tx_indices = [c.tx_index for c in change_list]
88+
89+
# Check both ordering and duplicates
90+
if tx_indices != sorted(tx_indices):
91+
raise BlockAccessListValidationError(
92+
f"Transaction indices not in ascending order in {field_name} of account "
93+
f"{account.address}. Got: {tx_indices}, Expected: {sorted(tx_indices)}"
94+
)
95+
96+
if len(tx_indices) != len(set(tx_indices)):
97+
duplicates = sorted(
98+
{
99+
idx
100+
for idx in tx_indices
101+
if tx_indices.count(idx) > 1
102+
}
103+
)
104+
raise BlockAccessListValidationError(
105+
f"Duplicate transaction indices in {field_name} of account "
106+
f"{account.address}. Duplicates: {duplicates}"
107+
)
108+
109+
# Check storage slot ordering
110+
for i in range(1, len(account.storage_changes)):
111+
if (
112+
account.storage_changes[i - 1].slot
113+
>= account.storage_changes[i].slot
114+
):
115+
raise BlockAccessListValidationError(
116+
f"Storage slots not in ascending order in account "
117+
f"{account.address}: {account.storage_changes[i - 1].slot} >= "
118+
f"{account.storage_changes[i].slot}"
119+
)
120+
121+
# Check transaction index ordering and uniqueness within storage slots
122+
for storage_slot in account.storage_changes:
123+
if not storage_slot.slot_changes:
124+
continue
125+
126+
tx_indices = [c.tx_index for c in storage_slot.slot_changes]
127+
128+
# Check both ordering and duplicates
129+
if tx_indices != sorted(tx_indices):
130+
raise BlockAccessListValidationError(
131+
f"Transaction indices not in ascending order in storage slot "
132+
f"{storage_slot.slot} of account {account.address}. "
133+
f"Got: {tx_indices}, Expected: {sorted(tx_indices)}"
134+
)
135+
136+
if len(tx_indices) != len(set(tx_indices)):
137+
duplicates = sorted(
138+
{
139+
idx
140+
for idx in tx_indices
141+
if tx_indices.count(idx) > 1
142+
}
143+
)
144+
raise BlockAccessListValidationError(
145+
f"Duplicate transaction indices in storage slot "
146+
f"{storage_slot.slot} of account {account.address}. "
147+
f"Duplicates: {duplicates}"
148+
)
149+
150+
# Check storage reads ordering
151+
for i in range(1, len(account.storage_reads)):
152+
if account.storage_reads[i - 1] >= account.storage_reads[i]:
153+
raise BlockAccessListValidationError(
154+
f"Storage reads not in ascending order in account "
155+
f"{account.address}: {account.storage_reads[i - 1]} >= "
156+
f"{account.storage_reads[i]}"
157+
)

0 commit comments

Comments
 (0)