diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 66bb09a9f85..334187f7110 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -79,6 +79,8 @@ increment_nonce, modify_state, set_account_balance, + track_ancestor_access, + track_bytecode_access, write_block_state_changes, ) from .transactions import ( @@ -252,6 +254,8 @@ def state_transition(chain: BlockChain, block: Block) -> None: storage_reads=set(), account_writes={}, storage_writes={}, + bytecode_accesses=set(), + ancestor_accesses=set(), ), block_gas_limit=block.header.gas_limit, block_hashes=get_last_256_block_hashes(chain), @@ -671,6 +675,8 @@ def process_system_transaction( account_writes={}, storage_writes={}, created_accounts=set(), + bytecode_accesses=set(), + ancestor_accesses=set(), ), ) @@ -704,6 +710,8 @@ def process_system_transaction( account_writes={}, storage_writes={}, created_accounts=set(), + bytecode_accesses=set(), + ancestor_accesses=set(), ), transient_storage={}, ) @@ -746,6 +754,7 @@ def process_checked_system_transaction( system_contract_code = get_account( block_env.state_tracking, target_address ).code + track_bytecode_access(block_env.state_tracking, system_contract_code) if len(system_contract_code) == 0: raise InvalidBlock( @@ -796,6 +805,7 @@ def process_unchecked_system_transaction( system_contract_code = get_account( block_env.state_tracking, target_address ).code + track_bytecode_access(block_env.state_tracking, system_contract_code) return process_system_transaction( block_env, target_address, @@ -851,6 +861,11 @@ def apply_body( target_address=HISTORY_STORAGE_ADDRESS, data=block_env.block_hashes[-1], # The parent hash ) + # Track parent block access for witness generation + track_ancestor_access( + block_env.state_tracking, + U64(block_env.number - Uint(1)), + ) for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) @@ -956,6 +971,8 @@ def process_transaction( account_writes={}, storage_writes={}, created_accounts=set(), + bytecode_accesses=set(), + ancestor_accesses=set(), ) # EIP-7928: Create a transaction-level StateChanges frame diff --git a/src/ethereum/forks/amsterdam/state_tracking.py b/src/ethereum/forks/amsterdam/state_tracking.py index 5add2ac2e84..c888fa8a784 100644 --- a/src/ethereum/forks/amsterdam/state_tracking.py +++ b/src/ethereum/forks/amsterdam/state_tracking.py @@ -7,7 +7,7 @@ from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from . import state as state_ from .fork_types import EMPTY_ACCOUNT, Account, Address @@ -35,6 +35,10 @@ class BlockStateTracking: Address, Dict[Bytes32, List[Tuple[BlockAccessIndex, U256]]] ] + bytecode_accesses: Set[Bytes] + + ancestor_accesses: Set[U64] + @dataclass class TxStateTracking: @@ -52,6 +56,10 @@ class TxStateTracking: created_accounts: Set[Address] + bytecode_accesses: Set[Bytes] + + ancestor_accesses: Set[U64] + # get_account_optional # set_account @@ -162,6 +170,8 @@ def incorporate_tx_state_into_parent(tx_state: TxStateTracking) -> None: for address, items in tx_state.storage_writes.items(): for key, value in items.items(): set_storage(parent, address, key, value) + parent.bytecode_accesses |= tx_state.bytecode_accesses + parent.ancestor_accesses |= tx_state.ancestor_accesses def write_block_state_changes(block_state: BlockStateTracking) -> None: @@ -193,6 +203,8 @@ def copy_tx_state_tracking(tx_state: TxStateTracking) -> TxStateTracking: account_writes=tx_state.account_writes.copy(), storage_writes=new_storage_writes, created_accounts=tx_state.created_accounts.copy(), + bytecode_accesses=tx_state.bytecode_accesses.copy(), + ancestor_accesses=tx_state.ancestor_accesses.copy(), ) @@ -518,3 +530,43 @@ def get_storage_original( return U256(0) return get_storage(state.parent, address, key) + + +def track_bytecode_access( + state: TxStateTracking | BlockStateTracking, + code: Bytes, +) -> None: + """ + Record that bytecode was accessed during execution. + + Parameters + ---------- + state : + The state tracking object. + code : + The bytecode that was accessed. + + """ + state.bytecode_accesses.add(code) + + +def track_ancestor_access( + state: TxStateTracking | BlockStateTracking, + block_number: U64, +) -> None: + """ + Record that an ancestor block was accessed. + + Called by: + - BLOCKHASH opcode (when returning valid hash) + - System contracts reading parent headers (EIP-2935) + + Parameters + ---------- + state : + The state tracking object. + block_number : + The block number that was accessed. + + """ + state.ancestor_accesses.add(block_number) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 6af99660be5..df0cb6eae5b 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -23,6 +23,7 @@ get_account, increment_nonce, set_authority_code, + track_bytecode_access, ) from ..utils.hexadecimal import hex_to_address from ..vm.gas import GAS_COLD_ACCOUNT_ACCESS, GAS_WARM_ACCESS @@ -145,6 +146,7 @@ def calculate_delegation_cost( """ state = evm.state_tracking code = get_account(state, address).code + track_bytecode_access(evm.state_tracking, code) track_address(evm.state_changes, address) if not is_valid_delegation(code): diff --git a/src/ethereum/forks/amsterdam/vm/instructions/block.py b/src/ethereum/forks/amsterdam/vm/instructions/block.py index e563a2e96e8..d06656c1a79 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/block.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/block.py @@ -11,8 +11,9 @@ Implementations of the EVM block instructions. """ -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ...state_tracking import track_ancestor_access from .. import Evm from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas from ..stack import pop, push @@ -57,6 +58,7 @@ def block_hash(evm: Evm) -> None: current_block_hash = evm.message.block_env.block_hashes[ -(current_block_number - block_number) ] + track_ancestor_access(evm.state_tracking, U64(block_number)) push(evm.stack, U256.from_be_bytes(current_block_hash)) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/environment.py b/src/ethereum/forks/amsterdam/vm/instructions/environment.py index 3eda99ed082..9cc2ed1aa53 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/environment.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/environment.py @@ -20,7 +20,7 @@ # track_address_access removed - now using state_changes.track_address() from ...fork_types import EMPTY_ACCOUNT from ...state_tracker import track_address -from ...state_tracking import get_account +from ...state_tracking import get_account, track_bytecode_access from ...utils.address import to_address_masked from ...vm.memory import buffer_read, memory_write from .. import Evm @@ -356,6 +356,7 @@ def extcodesize(evm: Evm) -> None: # OPERATION code = get_account(evm.state_tracking, address).code + track_bytecode_access(evm.state_tracking, code) track_address(evm.state_changes, address) codesize = U256(len(code)) @@ -402,6 +403,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.state_tracking, address).code + track_bytecode_access(evm.state_tracking, code) track_address(evm.state_changes, address) value = buffer_read(code, code_start_index, size) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 8288ca17a19..ddf72e05699 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -32,6 +32,7 @@ is_account_alive, move_ether, set_account_balance, + track_bytecode_access, ) from ...utils.address import ( compute_contract_address, @@ -458,6 +459,7 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(code_address) code = get_account(state, code_address).code + track_bytecode_access(evm.state_tracking, code) message_call_gas = calculate_message_call_gas( value, @@ -561,6 +563,7 @@ def callcode(evm: Evm) -> None: evm.accessed_addresses.add(code_address) code = get_account(state, code_address).code + track_bytecode_access(evm.state_tracking, code) message_call_gas = calculate_message_call_gas( value, @@ -757,6 +760,7 @@ def delegatecall(evm: Evm) -> None: evm.accessed_addresses.add(code_address) code = get_account(state, code_address).code + track_bytecode_access(evm.state_tracking, code) message_call_gas = calculate_message_call_gas( U256(0), @@ -847,6 +851,7 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(code_address) code = get_account(state, code_address).code + track_bytecode_access(evm.state_tracking, code) message_call_gas = calculate_message_call_gas( U256(0), diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 3af11550ed0..d7eada01f8f 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -50,6 +50,7 @@ mark_account_created, move_ether, set_code, + track_bytecode_access, ) from ..vm import Message from ..vm.eoa_delegation import get_delegated_code_address, set_delegation @@ -141,6 +142,7 @@ def process_message_call(message: Message) -> MessageCallOutput: message.disable_precompiles = True message.accessed_addresses.add(delegated_address) message.code = get_account(state_tracking, delegated_address).code + track_bytecode_access(message.state_tracking, message.code) message.code_address = delegated_address track_address(message.block_env.state_changes, delegated_address) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index d45ef554b4b..d3d36213cbc 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -321,6 +321,8 @@ def block_environment(self) -> Any: storage_reads=set(), account_writes={}, storage_writes={}, + bytecode_accesses=set(), + ancestor_accesses=set(), ) return block_environment(**kw_arguments) @@ -397,6 +399,11 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: target_address=self.fork.HISTORY_STORAGE_ADDRESS, data=block_env.block_hashes[-1], # The parent hash ) + # Track parent block access for witness generation + self.fork._module("state_tracking").track_ancestor_access( + block_env.state_tracking, + U64(block_env.number - Uint(1)), + ) if self.fork.has_beacon_roots_address: self.fork.process_unchecked_system_transaction(