diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index e11f0527e0..24a9f60272 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -35,7 +35,6 @@ TransientStorage, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -538,15 +537,6 @@ def process_system_transaction( system_tx_output = process_message_call(system_tx_message) - # TODO: Empty accounts in post-merge forks are impossible - # see Ethereum Improvement Proposal 7523. - # This line is only included to support invalid tests in the test suite - # and will have to be removed in the future. - # See https://github.com/ethereum/execution-specs/issues/955 - destroy_touched_empty_accounts( - block_env.state, system_tx_output.touched_accounts - ) - return system_tx_output @@ -722,8 +712,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/cancun/state.py b/src/ethereum/cancun/state.py index 4f9d5ff4b2..69aaa042d1 100644 --- a/src/ethereum/cancun/state.py +++ b/src/ethereum/cancun/state.py @@ -510,6 +510,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -556,22 +558,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -702,20 +688,3 @@ def set_transient_storage( trie_set(trie, key, value) if trie._data == {}: del transient_storage._tries[address] - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Set[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Set[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index 811bbab24e..b77c481c82 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, VersionedHash -from ..state import State, TransientStorage, account_exists_and_is_empty +from ..state import State, TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -145,7 +144,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -167,11 +165,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -187,18 +180,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index 9e503d4a72..5e5b3cf21b 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -546,10 +545,6 @@ def selfdestruct(evm: Evm) -> None: set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/cancun/vm/interpreter.py b/src/ethereum/cancun/vm/interpreter.py index 36a73f46b0..b120e9e6f1 100644 --- a/src/ethereum/cancun/vm/interpreter.py +++ b/src/ethereum/cancun/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -232,8 +221,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state, transient_storage) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -281,7 +268,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/exceptions.py b/src/ethereum/exceptions.py index 7fd959d9d3..4681aeed05 100644 --- a/src/ethereum/exceptions.py +++ b/src/ethereum/exceptions.py @@ -16,6 +16,12 @@ class InvalidBlock(EthereumException): """ +class StateWithEmptyAccount(EthereumException): + """ + Thrown when the state has empty account. + """ + + class InvalidTransaction(EthereumException): """ Thrown when a transaction being processed is found to be invalid. diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index 43c27acc71..ef55c4605a 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -34,7 +34,6 @@ State, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -564,8 +563,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used receipt = make_receipt( diff --git a/src/ethereum/paris/state.py b/src/ethereum/paris/state.py index 3650999343..88494ab2fb 100644 --- a/src/ethereum/paris/state.py +++ b/src/ethereum/paris/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -477,6 +477,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -523,22 +525,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -611,20 +597,3 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Iterable[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Iterable[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/paris/vm/__init__.py b/src/ethereum/paris/vm/__init__.py index fe4d472966..9af7fd2f74 100644 --- a/src/ethereum/paris/vm/__init__.py +++ b/src/ethereum/paris/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt from ..fork_types import Address -from ..state import State, account_exists_and_is_empty +from ..state import State from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -133,7 +132,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -155,11 +153,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -175,18 +168,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index c08b42ccb8..8141866c5e 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -524,10 +523,6 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/paris/vm/interpreter.py b/src/ethereum/paris/vm/interpreter.py index fe3ae8d4cc..77f6dc2f1e 100644 --- a/src/ethereum/paris/vm/interpreter.py +++ b/src/ethereum/paris/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -230,8 +219,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -279,7 +266,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 0ec47ccd2f..90748bd88a 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -42,7 +42,6 @@ TransientStorage, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -571,15 +570,6 @@ def process_system_transaction( system_tx_output = process_message_call(system_tx_message) - # TODO: Empty accounts in post-merge forks are impossible - # see Ethereum Improvement Proposal 7523. - # This line is only included to support invalid tests in the test suite - # and will have to be removed in the future. - # See https://github.com/ethereum/execution-specs/issues/955 - destroy_touched_empty_accounts( - block_env.state, system_tx_output.touched_accounts - ) - return system_tx_output @@ -830,8 +820,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py index 4f9d5ff4b2..69aaa042d1 100644 --- a/src/ethereum/prague/state.py +++ b/src/ethereum/prague/state.py @@ -510,6 +510,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -556,22 +558,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -702,20 +688,3 @@ def set_transient_storage( trie_set(trie, key, value) if trie._data == {}: del transient_storage._tries[address] - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Set[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Set[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 9e91cebc2c..429fa143d4 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, Authorization, VersionedHash -from ..state import State, TransientStorage, account_exists_and_is_empty +from ..state import State, TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -150,7 +149,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -172,11 +170,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -192,18 +185,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 7e8004b1ea..3522be427f 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -574,10 +573,6 @@ def selfdestruct(evm: Evm) -> None: set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index 5856c5af9c..61c7361c40 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -32,7 +32,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -43,7 +42,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.eoa_delegation import set_delegation @@ -77,16 +75,14 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. - 7. `return_data`: The output of the execution. + 5. `error`: The error from the execution if any. + 6. `return_data`: The output of the execution. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] return_data: Bytes @@ -118,7 +114,6 @@ def process_message_call(message: Message) -> MessageCallOutput: U256(0), tuple(), set(), - set(), AddressCollision(), Bytes(b""), ) @@ -128,19 +123,13 @@ def process_message_call(message: Message) -> MessageCallOutput: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -153,7 +142,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, return_data=evm.output, ) @@ -243,8 +231,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state, transient_storage) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -292,7 +278,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 3ff1dd82a4..ed315adcfd 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -34,7 +34,6 @@ State, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -577,8 +576,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used receipt = make_receipt( diff --git a/src/ethereum/shanghai/state.py b/src/ethereum/shanghai/state.py index 3650999343..88494ab2fb 100644 --- a/src/ethereum/shanghai/state.py +++ b/src/ethereum/shanghai/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -477,6 +477,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -523,22 +525,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -611,20 +597,3 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Iterable[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Iterable[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/shanghai/vm/__init__.py b/src/ethereum/shanghai/vm/__init__.py index 3d48ccaafe..a3706a9ee9 100644 --- a/src/ethereum/shanghai/vm/__init__.py +++ b/src/ethereum/shanghai/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address -from ..state import State, account_exists_and_is_empty +from ..state import State from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -138,7 +137,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -160,11 +158,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -180,18 +173,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 86001b1646..252ee5d9ed 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -547,10 +546,6 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/shanghai/vm/interpreter.py b/src/ethereum/shanghai/vm/interpreter.py index 431bda94f9..ecd04d2468 100644 --- a/src/ethereum/shanghai/vm/interpreter.py +++ b/src/ethereum/shanghai/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -230,8 +219,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -279,7 +266,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index 621a69eda3..df11319bcb 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -11,6 +11,7 @@ from ethereum_types.numeric import U256 from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import StateWithEmptyAccount from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes8, @@ -60,6 +61,7 @@ def json_to_state(self, raw: Any) -> Any: """Converts json state data to a state object""" state = self.fork.State() set_storage = self.fork.set_storage + EMPTY_ACCOUNT = self.fork.EMPTY_ACCOUNT for address_hex, account_state in raw.items(): address = self.fork.hex_to_address(address_hex) @@ -68,6 +70,9 @@ def json_to_state(self, raw: Any) -> Any: balance=U256(hex_to_uint(account_state.get("balance", "0x0"))), code=hex_to_bytes(account_state.get("code", "")), ) + if self.fork.proof_of_stake and account == EMPTY_ACCOUNT: + raise StateWithEmptyAccount(f"Empty account at {address_hex}.") + self.fork.set_account(state, address, account) for k, v in account_state.get("storage", {}).items(): diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 13904e90d5..85017c1002 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -172,6 +172,11 @@ def Bloom(self) -> Any: """Bloom class of the fork""" return self._module("fork_types").Bloom + @property + def EMPTY_ACCOUNT(self) -> Any: + """EMPTY_ACCOUNT of the fork""" + return self._module("fork_types").EMPTY_ACCOUNT + @property def Header(self) -> Any: """Header class of the fork""" diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index 66d183523b..a2c9602495 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -6,6 +6,7 @@ import pytest +from ethereum.exceptions import StateWithEmptyAccount from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools import create_parser from ethereum_spec_tools.evm_tools.statetest import read_test_cases @@ -126,7 +127,11 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: ] t8n_options = parser.parse_args(t8n_args) - t8n = T8N(t8n_options, sys.stdout, in_stream) + try: + t8n = T8N(t8n_options, sys.stdout, in_stream) + except StateWithEmptyAccount as e: + pytest.xfail(str(e)) + t8n.run_state_test() assert hex_to_bytes(post_hash) == t8n.result.state_root diff --git a/tests/helpers/load_state_tests.py b/tests/helpers/load_state_tests.py index 94ada0bffa..6372065674 100644 --- a/tests/helpers/load_state_tests.py +++ b/tests/helpers/load_state_tests.py @@ -13,7 +13,7 @@ from ethereum_types.numeric import U64 from ethereum.crypto.hash import keccak256 -from ethereum.exceptions import EthereumException +from ethereum.exceptions import EthereumException, StateWithEmptyAccount from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load @@ -56,9 +56,14 @@ def run_blockchain_st_test(test_case: Dict, load: Load) -> None: genesis_rlp = hex_to_bytes(json_data["genesisRLP"]) assert rlp.encode(genesis_block) == genesis_rlp + try: + state = load.json_to_state(json_data["pre"]) + except StateWithEmptyAccount as e: + pytest.xfail(str(e)) + chain = load.fork.BlockChain( blocks=[genesis_block], - state=load.json_to_state(json_data["pre"]), + state=state, chain_id=U64(json_data["genesisBlockHeader"].get("chainId", 1)), )