diff --git a/src/ethereum_test_specs/blockchain.py b/src/ethereum_test_specs/blockchain.py index 131794da04d..bae78a09eea 100644 --- a/src/ethereum_test_specs/blockchain.py +++ b/src/ethereum_test_specs/blockchain.py @@ -224,6 +224,10 @@ class Block(Header): """ Custom list of requests to embed in this block. """ + expected_post_state: Alloc | None = None + """ + Post state for verification after block execution in BlockchainTest + """ def set_environment(self, env: Environment) -> Environment: """ @@ -491,10 +495,13 @@ def network_info(self, fork: Fork, eips: Optional[List[int]] = None): else fork.blockchain_test_network_name() ) - def verify_post_state(self, t8n, alloc: Alloc): + def verify_post_state(self, t8n, t8n_state: Alloc, expected_state: Alloc | None = None): """Verify post alloc after all block/s or payload/s are generated.""" try: - self.post.verify_post_alloc(alloc) + if expected_state: + expected_state.verify_post_alloc(t8n_state) + else: + self.post.verify_post_alloc(t8n_state) except Exception as e: print_traces(t8n.get_traces()) raise e @@ -570,7 +577,12 @@ def make_fixture( ), ) - self.verify_post_state(t8n, alloc) + if block.expected_post_state: + self.verify_post_state( + t8n, t8n_state=alloc, expected_state=block.expected_post_state + ) + + self.verify_post_state(t8n, t8n_state=alloc) return Fixture( fork=self.network_info(fork, eips), genesis=genesis.header, @@ -622,13 +634,19 @@ def make_hive_fixture( alloc = new_alloc env = apply_new_parent(env, header) head_hash = header.block_hash + + if block.expected_post_state: + self.verify_post_state( + t8n, t8n_state=alloc, expected_state=block.expected_post_state + ) + fcu_version = fork.engine_forkchoice_updated_version(header.number, header.timestamp) assert ( fcu_version is not None ), "A hive fixture was requested but no forkchoice update is defined. The framework should" " never try to execute this test case." - self.verify_post_state(t8n, alloc) + self.verify_post_state(t8n, t8n_state=alloc) sync_payload: Optional[FixtureEngineNewPayload] = None if self.verify_sync: diff --git a/src/ethereum_test_specs/tests/test_expect.py b/src/ethereum_test_specs/tests/test_expect.py index a18a5f495d4..44b87512905 100644 --- a/src/ethereum_test_specs/tests/test_expect.py +++ b/src/ethereum_test_specs/tests/test_expect.py @@ -9,8 +9,10 @@ from ethereum_test_exceptions import TransactionException from ethereum_test_fixtures import BlockchainFixture, FixtureFormat, StateFixture from ethereum_test_forks import Fork, get_deployed_forks +from ethereum_test_tools import Block from ethereum_test_types import Alloc, Environment, Storage, Transaction, TransactionReceipt +from ..blockchain import BlockchainEngineFixture, BlockchainTest from ..helpers import ( TransactionExceptionMismatchError, TransactionReceiptMismatchError, @@ -349,3 +351,99 @@ def test_transaction_expectation( else: with pytest.raises(exception_type) as _: state_test.generate(request=None, t8n=t8n, fork=fork, fixture_format=fixture_format) + + +@pytest.mark.parametrize( + "intermediate_state,expected_exception", + [ + pytest.param( + { + TestAddress: Account(nonce=1), + Address(0x01): Account(balance=1), + }, + None, + id="NoException", + ), + pytest.param( + { + TestAddress: Account(nonce=2), + Address(0x01): Account(balance=1), + }, + Account.NonceMismatchError, + id="NonceMismatchError", + ), + pytest.param( + { + TestAddress: Account(nonce=1), + Address(0x01): Account(balance=2), + }, + Account.BalanceMismatchError, + id="BalanceMismatchError", + ), + ], +) +@pytest.mark.parametrize( + "fixture_format", + [ + BlockchainFixture, + BlockchainEngineFixture, + ], +) +def test_block_intermediate_state( + pre, t8n, fork, fixture_format: FixtureFormat, intermediate_state, expected_exception +): + """Validate the state when building blockchain.""" + env = Environment() + + to = Address(0x01) + tx = Transaction(gas_limit=100_000, to=to, value=1, nonce=0, secret_key=TestPrivateKey) + tx_2 = Transaction(gas_limit=100_000, to=to, value=1, nonce=1, secret_key=TestPrivateKey) + + block_1 = Block( + txs=[tx], + expected_post_state={ + TestAddress: Account(nonce=1), + to: Account(balance=1), + }, + ) + + block_2 = Block(txs=[], expected_post_state=intermediate_state) + + block_3 = Block( + txs=[tx_2], + expected_post_state={ + TestAddress: Account(nonce=2), + to: Account(balance=2), + }, + ) + + if expected_exception: + with pytest.raises(expected_exception) as _: + BlockchainTest( + genesis_environment=env, + fork=fork, + t8n=t8n, + pre=pre, + post=block_3.expected_post_state, + blocks=[block_1, block_2, block_3], + ).generate( + request=None, # type: ignore + t8n=t8n, + fork=fork, + fixture_format=fixture_format, + ) + return + else: + BlockchainTest( + genesis_environment=env, + fork=fork, + t8n=t8n, + pre=pre, + post=block_3.expected_post_state, + blocks=[block_1, block_2, block_3], + ).generate( + request=None, # type: ignore + t8n=t8n, + fork=fork, + fixture_format=fixture_format, + ) diff --git a/tests/frontier/examples/__init__.py b/tests/frontier/examples/__init__.py new file mode 100644 index 00000000000..88347b552d4 --- /dev/null +++ b/tests/frontier/examples/__init__.py @@ -0,0 +1 @@ +"""Test examples, patterns, templates to use in .py tests.""" diff --git a/tests/frontier/examples/test_block_intermediate_state.py b/tests/frontier/examples/test_block_intermediate_state.py new file mode 100644 index 00000000000..299134173be --- /dev/null +++ b/tests/frontier/examples/test_block_intermediate_state.py @@ -0,0 +1,50 @@ +"""Test the SELFDESTRUCT opcode.""" + +import pytest + +from ethereum_test_tools import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Environment, + Transaction, +) + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.valid_until("Homestead") +def test_block_intermidiate_state(blockchain_test: BlockchainTestFiller, pre: Alloc): + """Verify intermidiate block states.""" + env = Environment() + sender = pre.fund_eoa() + + tx = Transaction(gas_limit=100_000, to=None, data=b"", sender=sender, protected=False) + tx_2 = Transaction(gas_limit=100_000, to=None, data=b"", sender=sender, protected=False) + + block_1 = Block( + txs=[tx], + expected_post_state={ + sender: Account( + nonce=1, + ), + }, + ) + + block_2 = Block(txs=[]) + + block_3 = Block( + txs=[tx_2], + expected_post_state={ + sender: Account( + nonce=2, + ), + }, + ) + + blockchain_test( + genesis_environment=env, + pre=pre, + post=block_3.expected_post_state, + blocks=[block_1, block_2, block_3], + ) diff --git a/whitelist.txt b/whitelist.txt index eaeeb435d90..d5bd0b57c72 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -253,6 +253,7 @@ isort isort's ispkg itemName +intermidiate javascripts jimporter joinpath