Skip to content

Commit 59b2dc7

Browse files
committed
feat: Validate t8n BAL does not have duplicate entries for the same tx_index
1 parent e842859 commit 59b2dc7

File tree

2 files changed

+118
-25
lines changed

2 files changed

+118
-25
lines changed

src/ethereum_test_types/block_access_list/expectations.py

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -194,21 +194,33 @@ def _validate_bal_ordering(bal: "BlockAccessList") -> None:
194194
f"{bal.root[i - 1].address} >= {bal.root[i].address}"
195195
)
196196

197-
# Check transaction index ordering within accounts
197+
# Check transaction index ordering and uniqueness within accounts
198198
for account in bal.root:
199-
change_lists: List[BlockAccessListChangeLists] = [
200-
account.nonce_changes,
201-
account.balance_changes,
202-
account.code_changes,
199+
changes_to_check: List[tuple[str, BlockAccessListChangeLists]] = [
200+
("nonce_changes", account.nonce_changes),
201+
("balance_changes", account.balance_changes),
202+
("code_changes", account.code_changes),
203203
]
204-
for change_list in change_lists:
205-
for i in range(1, len(change_list)):
206-
if change_list[i - 1].tx_index >= change_list[i].tx_index:
207-
raise BlockAccessListValidationError(
208-
f"Transaction indices not in ascending order in account "
209-
f"{account.address}: {change_list[i - 1].tx_index} >= "
210-
f"{change_list[i].tx_index}"
211-
)
204+
205+
for field_name, change_list in changes_to_check:
206+
if not change_list:
207+
continue
208+
209+
tx_indices = [c.tx_index for c in change_list]
210+
211+
# Check both ordering and duplicates
212+
if tx_indices != sorted(tx_indices):
213+
raise BlockAccessListValidationError(
214+
f"Transaction indices not in ascending order in {field_name} of account "
215+
f"{account.address}. Got: {tx_indices}, Expected: {sorted(tx_indices)}"
216+
)
217+
218+
if len(tx_indices) != len(set(tx_indices)):
219+
duplicates = sorted({idx for idx in tx_indices if tx_indices.count(idx) > 1})
220+
raise BlockAccessListValidationError(
221+
f"Duplicate transaction indices in {field_name} of account "
222+
f"{account.address}. Duplicates: {duplicates}"
223+
)
212224

213225
# Check storage slot ordering
214226
for i in range(1, len(account.storage_changes)):
@@ -219,19 +231,28 @@ def _validate_bal_ordering(bal: "BlockAccessList") -> None:
219231
f"{account.storage_changes[i].slot}"
220232
)
221233

222-
# Check transaction index ordering within storage slots
234+
# Check transaction index ordering and uniqueness within storage slots
223235
for storage_slot in account.storage_changes:
224-
for i in range(1, len(storage_slot.slot_changes)):
225-
if (
226-
storage_slot.slot_changes[i - 1].tx_index
227-
>= storage_slot.slot_changes[i].tx_index
228-
):
229-
raise BlockAccessListValidationError(
230-
f"Transaction indices not in ascending order in storage slot "
231-
f"{storage_slot.slot} of account {account.address}: "
232-
f"{storage_slot.slot_changes[i - 1].tx_index} >= "
233-
f"{storage_slot.slot_changes[i].tx_index}"
234-
)
236+
if not storage_slot.slot_changes:
237+
continue
238+
239+
tx_indices = [c.tx_index for c in storage_slot.slot_changes]
240+
241+
# Check both ordering and duplicates
242+
if tx_indices != sorted(tx_indices):
243+
raise BlockAccessListValidationError(
244+
f"Transaction indices not in ascending order in storage slot "
245+
f"{storage_slot.slot} of account {account.address}. "
246+
f"Got: {tx_indices}, Expected: {sorted(tx_indices)}"
247+
)
248+
249+
if len(tx_indices) != len(set(tx_indices)):
250+
duplicates = sorted({idx for idx in tx_indices if tx_indices.count(idx) > 1})
251+
raise BlockAccessListValidationError(
252+
f"Duplicate transaction indices in storage slot "
253+
f"{storage_slot.slot} of account {account.address}. "
254+
f"Duplicates: {duplicates}"
255+
)
235256

236257
# Check storage reads ordering
237258
for i in range(1, len(account.storage_reads)):

src/ethereum_test_types/tests/test_block_access_lists.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,78 @@ def test_actual_bal_tx_indices_ordering(field_name):
334334
expectation.verify_against(actual_bal)
335335

336336

337+
@pytest.mark.parametrize(
338+
"field_name",
339+
["nonce_changes", "balance_changes", "code_changes"],
340+
)
341+
def test_actual_bal_duplicate_tx_indices(field_name):
342+
"""Test that actual BAL must not have duplicate tx indices in change lists."""
343+
addr = Address(0xA)
344+
345+
# Duplicate tx_index=1
346+
changes = []
347+
if field_name == "nonce_changes":
348+
changes = [
349+
BalNonceChange(tx_index=1, post_nonce=1),
350+
BalNonceChange(tx_index=1, post_nonce=2), # duplicate tx_index
351+
BalNonceChange(tx_index=2, post_nonce=3),
352+
]
353+
elif field_name == "balance_changes":
354+
changes = [
355+
BalBalanceChange(tx_index=1, post_balance=100),
356+
BalBalanceChange(tx_index=1, post_balance=200), # duplicate tx_index
357+
BalBalanceChange(tx_index=2, post_balance=300),
358+
]
359+
elif field_name == "code_changes":
360+
changes = [
361+
BalCodeChange(tx_index=1, new_code=b"code1"),
362+
BalCodeChange(tx_index=1, new_code=b""), # duplicate tx_index
363+
BalCodeChange(tx_index=2, new_code=b"code2"),
364+
]
365+
366+
actual_bal = BlockAccessList([BalAccountChange(address=addr, **{field_name: changes})])
367+
368+
expectation = BlockAccessListExpectation(account_expectations={})
369+
370+
with pytest.raises(
371+
BlockAccessListValidationError,
372+
match=f"Duplicate transaction indices in {field_name}.*Duplicates: \\[1\\]",
373+
):
374+
expectation.verify_against(actual_bal)
375+
376+
377+
def test_actual_bal_storage_duplicate_tx_indices():
378+
"""Test that storage changes must not have duplicate tx indices within same slot."""
379+
addr = Address(0xA)
380+
381+
# Create storage changes with duplicate tx_index within the same slot
382+
actual_bal = BlockAccessList(
383+
[
384+
BalAccountChange(
385+
address=addr,
386+
storage_changes=[
387+
BalStorageSlot(
388+
slot=0x01,
389+
slot_changes=[
390+
BalStorageChange(tx_index=1, post_value=0x100),
391+
BalStorageChange(tx_index=1, post_value=0x200), # duplicate tx_index
392+
BalStorageChange(tx_index=2, post_value=0x300),
393+
],
394+
)
395+
],
396+
)
397+
]
398+
)
399+
400+
expectation = BlockAccessListExpectation(account_expectations={})
401+
402+
with pytest.raises(
403+
BlockAccessListValidationError,
404+
match="Duplicate transaction indices in storage slot.*Duplicates: \\[1\\]",
405+
):
406+
expectation.verify_against(actual_bal)
407+
408+
337409
def test_expected_addresses_auto_sorted():
338410
"""
339411
Test that expected addresses are automatically sorted before comparison.

0 commit comments

Comments
 (0)