Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class RethExceptionMapper(ExceptionMapper):
# BAL Exceptions
BlockException.INVALID_BAL_HASH: (r"block access list hash mismatch"),
BlockException.INVALID_BLOCK_ACCESS_LIST: (
r"failed to decode block access list|"
r"block access list hash mismatch|"
r"BAL rejection: FinalHashMismatch|"
r"Bal error: Account .* not found in BAL|"
Expand Down
20 changes: 8 additions & 12 deletions packages/testing/src/execution_testing/specs/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,13 @@ def engine_payload_modifier(
self,
) -> "FixtureExecutionPayloadModifier | None":
"""
Propagate ``rlp_modifier``'s header changes to the engine payload.
Propagate header changes and explicit overrides to the engine payload.

The engine ``ExecutionPayload`` schema does not carry
``block_access_list_hash`` directly; the equivalent payload field is
the ``block_access_list`` body. So a header modifier that touches the
BAL hash needs to drive a matching change on the payload body.
``block_access_list_hash`` directly; the corresponding payload
field is the ``block_access_list`` body. Removing the header hash
therefore removes the payload field. Adding or changing the hash
is reflected in ``blockHash`` without synthesizing a BAL body.
"""
if self.engine_new_payload_slot_number is not None:
return FixtureExecutionPayloadModifier(
Expand All @@ -586,14 +587,9 @@ def engine_payload_modifier(
FixtureExecutionPayloadModifier.REMOVE_FIELD
),
)
# The user injected a header BAL hash; mirror that on the engine
# payload by forcing a body to be present. Its exact value is
# irrelevant for negative tests — a non-``None`` value is enough to
# make a payload-version mismatch detectable.
if self.block_access_list is None:
return FixtureExecutionPayloadModifier(
block_access_list=Bytes(b""),
)
# Do not introduce an unsupported pre-fork payload field when the
# intended defect is the header hash. API field-presence tests use
# an explicit engine_new_payload_block_access_list override.
return None

def get_fixture_engine_new_payload(self) -> FixtureEngineNewPayload:
Expand Down
32 changes: 18 additions & 14 deletions packages/testing/src/execution_testing/specs/tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
FixtureExecutionPayloadModifier,
FixtureHeader,
)
from execution_testing.forks import Amsterdam
from execution_testing.forks import Amsterdam, Fork, Osaka
from execution_testing.test_types import Alloc, Environment
from execution_testing.test_types.block_access_list import (
BlockAccessList,
Expand Down Expand Up @@ -166,6 +166,7 @@ def test_fixture_header_join(

def built_block(
*,
fork: Fork = Amsterdam,
rlp_modifier: Header | None = None,
block_access_list: BlockAccessList | None = None,
engine_new_payload_block_access_list: Bytes | None = None,
Expand All @@ -181,7 +182,7 @@ def built_block(
withdrawals=None,
requests=None,
result=result_empty,
fork=Amsterdam,
fork=fork,
rlp_modifier=rlp_modifier,
block_access_list=block_access_list,
engine_new_payload_block_access_list=engine_new_payload_block_access_list,
Expand Down Expand Up @@ -228,18 +229,19 @@ def test_remove_bal_hash_removes_body_from_payload(self) -> None:
FixtureExecutionPayloadModifier.REMOVE_FIELD
)

def test_inject_bal_hash_on_pre_fork_adds_body(self) -> None:
def test_inject_bal_hash_on_pre_fork_keeps_body_absent(self) -> None:
"""
Injecting a header BAL hash on a block that has no body (pre-fork)
triggers a body to be added to the engine payload, so a payload-
version mismatch is detectable.
Keep pre-fork payload parameters valid when only the header hash
is corrupted, so rejection tests the block hash alone.
"""
modifier = built_block(
rlp_modifier=Header(block_access_list_hash=Hash(0)),
block_access_list=None,
).engine_payload_modifier()
assert isinstance(modifier, FixtureExecutionPayloadModifier)
assert modifier.block_access_list == Bytes(b"")
assert (
built_block(
fork=Osaka,
rlp_modifier=Header(block_access_list_hash=Hash(0)),
block_access_list=None,
).engine_payload_modifier()
is None
)

def test_inject_bal_hash_on_post_fork_leaves_body_alone(self) -> None:
"""
Expand All @@ -255,10 +257,12 @@ def test_inject_bal_hash_on_post_fork_leaves_body_alone(self) -> None:
is None
)

def test_empty_bytes_override_sends_raw_body(self) -> None:
@pytest.mark.parametrize("fork", [Osaka, Amsterdam])
def test_empty_bytes_override_sends_raw_body(self, fork: Fork) -> None:
"""Raw `Bytes` (e.g. the invalid `0x`) are sent verbatim."""
modifier = built_block(
engine_new_payload_block_access_list=Bytes(b"")
fork=fork,
engine_new_payload_block_access_list=Bytes(b""),
).engine_payload_modifier()
assert isinstance(modifier, FixtureExecutionPayloadModifier)
assert modifier.block_access_list == Bytes(b"")
Expand Down
2 changes: 1 addition & 1 deletion tests/amsterdam/eip7928_block_level_access_lists/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ class ReferenceSpec:

ref_spec_7928 = ReferenceSpec(
git_path="EIPS/eip-7928.md",
version="f834f0004aa5110a5f1ac0d6b80e3dc4b842d040",
version="d2a64c2d4cc44f2f507577d0ebfb110dcc21d358",
)
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ def test_bal_2930_account_listed_but_untouched(
def test_bal_2930_precompile_listed_but_untouched(
pre: Alloc,
state_test: StateTestFiller,
precompile: int,
precompile: Address,
) -> None:
"""
Ensure a precompile named in the access list but never called stays
Expand Down Expand Up @@ -1068,7 +1068,7 @@ def test_bal_zero_value_transfer(
],
),
# Include the address; omit from balance_changes.
bob: BalAccountExpectation(balance_changes=[]),
bob: BalAccountExpectation.empty(),
}
),
)
Expand Down Expand Up @@ -2103,7 +2103,7 @@ def test_bal_precompile_funded(
def test_bal_precompile_call_opcode(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
precompile: int,
precompile: Address,
call_opcode: Op,
) -> None:
"""
Expand Down Expand Up @@ -2182,10 +2182,10 @@ def test_bal_nonexistent_value_transfer(
BalBalanceChange(
block_access_index=1, post_balance=value
)
]
if value > 0
else [],
),
],
)
if value > 0
else BalAccountExpectation.empty(),
}
),
)
Expand Down Expand Up @@ -2360,10 +2360,10 @@ def test_bal_nonexistent_account_access_value_transfer(
block_access_index=1,
post_balance=bob_final_balance,
)
]
if bob_has_balance_change
else [],
),
],
)
if bob_has_balance_change
else BalAccountExpectation.empty(),
}
),
)
Expand Down Expand Up @@ -4278,7 +4278,8 @@ def test_bal_gas_limit_boundary(
# charge that would otherwise inflate the tx's gas needs past
# the BAL-sized ``block_gas_limit``.
bob = pre.fund_eoa(amount=1)
# alice (sender) + bob (recipient) + coinbase (EIP-3651 warm).
# alice (sender) + bob (recipient) + coinbase, touched by the
# zero priority-fee credit.
extra_items += 3
txs.append(
Transaction(
Expand Down Expand Up @@ -4400,7 +4401,8 @@ def test_bal_gas_limit_boundary_storage_keys(
for _ in range(2)
]

# alice + counter + coinbase (EIP-3651 warm), then one item per key.
# alice + counter + coinbase (touched by the zero priority-fee
# credit), then one item per key.
storage_keys = [written_slot, read_slot]
total_items = fork.empty_block_bal_item_count() + 3 + len(storage_keys)
gas_limit = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1890,25 +1890,33 @@ def test_bal_invalid_extraneous_coinbase(
pytest.param(b"\xc1", id="rlp_truncated_list"),
],
)
@pytest.mark.parametrize("header_commits_to", ["canonical_rlp", "payload_rlp"])
def test_bal_invalid_engine_payload_encoding(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
invalid_bal_payload: bytes,
header_commits_to: str,
) -> None:
"""
Reject a `newPayload` whose `blockAccessList` does not decode as an RLP
list: the empty byte string `0x` (an empty BAL is `0xc0`), the RLP
empty byte string `0x80` (valid RLP but not a list), or a truncated
list header `0xc1`.

The field is present but not a valid encoding, so the payload is
invalid rather than the request being malformed.
Reject malformed BAL RLP with both matching and mismatched header
commitments, so a block-hash check cannot hide missing RLP validation.
"""
sender = pre.fund_eoa()
receiver = pre.nonexistent_account()

tx = Transaction(sender=sender, to=receiver)

expectation = BlockAccessListExpectation()
exceptions = [BlockException.INVALID_BLOCK_ACCESS_LIST]
if header_commits_to == "canonical_rlp":
expectation = expectation.modify_rlp(
lambda _: Bytes(invalid_bal_payload)
)
exceptions.append(BlockException.INVALID_BLOCK_HASH)
else:
expectation = expectation.modify(
override_rlp(lambda _: Bytes(invalid_bal_payload))
)

blockchain_test(
pre=pre,
post={
Expand All @@ -1918,10 +1926,8 @@ def test_bal_invalid_engine_payload_encoding(
blocks=[
Block(
txs=[tx],
engine_new_payload_block_access_list=Bytes(
invalid_bal_payload
),
exception=BlockException.INVALID_BLOCK_ACCESS_LIST,
expected_block_access_list=expectation,
exception=exceptions,
)
],
)
Expand Down Expand Up @@ -1960,6 +1966,10 @@ def test_bal_invalid_non_minimal_scalar_encoding(
a matching hash and accepts. With the header committing to the
payload RLP, a client that decodes leniently and hashes the bytes as
received accepts instead.

A strict client may notice the bad scalar while decoding, or only
once the hash it derives from the payload disagrees with the header;
both verdicts reject the block, so both are accepted.
"""
alice = pre.fund_eoa()
oracle = pre.deploy_contract(code=Op.SSTORE(1, 1) + Op.SLOAD(2))
Expand Down Expand Up @@ -2020,7 +2030,10 @@ def test_bal_invalid_non_minimal_scalar_encoding(
blocks=[
Block(
txs=[tx],
exception=BlockException.INVALID_BLOCK_ACCESS_LIST,
exception=[
BlockException.INVALID_BLOCK_ACCESS_LIST,
BlockException.INVALID_BLOCK_HASH,
],
expected_block_access_list=expectation,
)
],
Expand Down
Loading