From 974cbcf13ea1ac285009a559bf524b1e14467ef4 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 18:31:55 +0100 Subject: [PATCH 01/11] add code --- pyproject.toml | 1 + src/ethereum/osaka/fork.py | 66 +++--- src/ethereum/osaka/utils/message.py | 4 +- src/ethereum/osaka/vm/__init__.py | 5 +- src/ethereum/osaka/vm/eoa_delegation.py | 21 +- .../osaka/vm/instructions/environment.py | 17 +- src/ethereum/osaka/vm/instructions/memory.py | 1 + src/ethereum/osaka/vm/instructions/storage.py | 24 +- src/ethereum/osaka/vm/instructions/system.py | 54 ++--- src/ethereum/osaka/vm/interpreter.py | 8 +- src/ethereum/state_oracle/__init__.py | 14 ++ src/ethereum/state_oracle/interface.py | 213 ++++++++++++++++++ src/ethereum/state_oracle/memory_oracle.py | 191 ++++++++++++++++ 13 files changed, 517 insertions(+), 102 deletions(-) create mode 100644 src/ethereum/state_oracle/__init__.py create mode 100644 src/ethereum/state_oracle/interface.py create mode 100644 src/ethereum/state_oracle/memory_oracle.py diff --git a/pyproject.toml b/pyproject.toml index a188a57c84..f5a711714c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ packages = [ "ethereum_spec_tools.lint", "ethereum_spec_tools.lint.lints", "ethereum", + "ethereum.state_oracle", "ethereum.frontier", "ethereum.frontier.utils", "ethereum.frontier.vm", diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index a4f3c231f4..e6e249df1c 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -54,13 +54,6 @@ from .state import ( State, TransientStorage, - account_exists_and_is_empty, - destroy_account, - get_account, - increment_nonce, - modify_state, - set_account_balance, - state_root, ) from .transactions import ( AccessListTransaction, @@ -189,7 +182,7 @@ def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: return recent_block_hashes -def state_transition(chain: BlockChain, block: Block) -> None: +def state_transition(chain: BlockChain, block: Block, oracle=None) -> None: """ Attempts to apply a block to an existing block chain. @@ -210,6 +203,8 @@ def state_transition(chain: BlockChain, block: Block) -> None: History and current state. block : Block to apply to `chain`. + oracle : MerkleOracle + State oracle for accessing blockchain state. Must be provided. """ if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE: raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE") @@ -218,9 +213,13 @@ def state_transition(chain: BlockChain, block: Block) -> None: if block.ommers != (): raise InvalidBlock + # Oracle must be provided + if oracle is None: + raise ValueError("Oracle parameter is required for state_transition") + block_env = vm.BlockEnvironment( chain_id=chain.chain_id, - state=chain.state, + oracle=oracle, block_gas_limit=block.header.gas_limit, block_hashes=get_last_256_block_hashes(chain), coinbase=block.header.coinbase, @@ -237,7 +236,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: transactions=block.transactions, withdrawals=block.withdrawals, ) - block_state_root = state_root(block_env.state) + block_state_root = block_env.oracle.state_root() transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -461,7 +460,7 @@ def check_transaction( raise BlobGasLimitExceededError("blob gas limit exceeded") sender_address = recover_sender(block_env.chain_id, tx) - sender_account = get_account(block_env.state, sender_address) + sender_account = block_env.oracle.get_account(sender_address) if isinstance( tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) @@ -666,7 +665,7 @@ def process_checked_system_transaction( system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = get_account(block_env.state, target_address).code + system_contract_code = block_env.oracle.get_account(target_address).code if len(system_contract_code) == 0: raise InvalidBlock( @@ -713,7 +712,7 @@ def process_unchecked_system_transaction( system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = get_account(block_env.state, target_address).code + system_contract_code = block_env.oracle.get_account(target_address).code return process_system_transaction( block_env, target_address, @@ -870,7 +869,7 @@ def process_transaction( tx=tx, ) - sender_account = get_account(block_env.state, sender) + sender_account = block_env.oracle.get_account(sender) if isinstance(tx, BlobTransaction): blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) @@ -880,13 +879,13 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(block_env.state, sender) + block_env.oracle.increment_nonce(sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee ) - set_account_balance( - block_env.state, sender, U256(sender_balance_after_gas_fee) + block_env.oracle.set_account_balance( + sender, U256(sender_balance_after_gas_fee) ) access_list_addresses = set() @@ -949,26 +948,29 @@ def process_transaction( transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas # refund gas - sender_balance_after_refund = get_account( - block_env.state, sender - ).balance + U256(gas_refund_amount) - set_account_balance(block_env.state, sender, sender_balance_after_refund) + current_sender_balance = block_env.oracle.get_account(sender).balance + sender_balance_after_refund = current_sender_balance + U256( + gas_refund_amount + ) + block_env.oracle.set_account_balance(sender, sender_balance_after_refund) # transfer miner fees - coinbase_balance_after_mining_fee = get_account( - block_env.state, block_env.coinbase - ).balance + U256(transaction_fee) + current_coinbase_balance = block_env.oracle.get_account( + block_env.coinbase + ).balance + coinbase_balance_after_mining_fee = current_coinbase_balance + U256( + transaction_fee + ) if coinbase_balance_after_mining_fee != 0: - set_account_balance( - block_env.state, + block_env.oracle.set_account_balance( block_env.coinbase, coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(block_env.state, block_env.coinbase): - destroy_account(block_env.state, block_env.coinbase) + elif block_env.oracle.account_exists_and_is_empty(block_env.coinbase): + block_env.oracle.destroy_account(block_env.coinbase) for address in tx_output.accounts_to_delete: - destroy_account(block_env.state, address) + block_env.oracle.destroy_account(address) block_output.block_gas_used += tx_gas_used_after_refund block_output.blob_gas_used += tx_blob_gas_used @@ -1008,10 +1010,10 @@ def increase_recipient_balance(recipient: Account) -> None: rlp.encode(wd), ) - modify_state(block_env.state, wd.address, increase_recipient_balance) + block_env.oracle.modify_state(wd.address, increase_recipient_balance) - if account_exists_and_is_empty(block_env.state, wd.address): - destroy_account(block_env.state, wd.address) + if block_env.oracle.account_exists_and_is_empty(wd.address): + block_env.oracle.destroy_account(wd.address) def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py index 7d0ee65131..ca32f4309a 100644 --- a/src/ethereum/osaka/utils/message.py +++ b/src/ethereum/osaka/utils/message.py @@ -54,7 +54,7 @@ def prepare_message( if isinstance(tx.to, Bytes0): current_target = compute_contract_address( tx_env.origin, - get_account(block_env.state, tx_env.origin).nonce - Uint(1), + block_env.oracle.get_account(tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") code = tx.data @@ -62,7 +62,7 @@ def prepare_message( elif isinstance(tx.to, Address): current_target = tx.to msg_data = tx.data - code = get_account(block_env.state, tx.to).code + code = block_env.oracle.get_account(tx.to).code code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py index 033293a5fd..a18365dc51 100644 --- a/src/ethereum/osaka/vm/__init__.py +++ b/src/ethereum/osaka/vm/__init__.py @@ -21,10 +21,11 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException +from ethereum.state_oracle import MerkleOracle from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, Authorization, VersionedHash -from ..state import State, TransientStorage +from ..state import TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie @@ -38,7 +39,7 @@ class BlockEnvironment: """ chain_id: U64 - state: State + oracle: MerkleOracle block_gas_limit: Uint block_hashes: List[Hash32] coinbase: Address diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py index 1fe2e1e7bd..28b3743224 100644 --- a/src/ethereum/osaka/vm/eoa_delegation.py +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -2,7 +2,6 @@ Set EOA account code. """ - from typing import Optional, Tuple from ethereum_rlp import rlp @@ -20,7 +19,7 @@ from . import Evm, Message SET_CODE_TX_MAGIC = b"\x05" -EOA_DELEGATION_MARKER = b"\xEF\x01\x00" +EOA_DELEGATION_MARKER = b"\xef\x01\x00" EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) EOA_DELEGATED_CODE_LENGTH = 23 PER_EMPTY_ACCOUNT_COST = 25000 @@ -130,8 +129,8 @@ def access_delegation( delegation : `Tuple[bool, Address, Bytes, Uint]` The delegation address, code, and access gas cost. """ - state = evm.message.block_env.state - code = get_account(state, address).code + oracle = evm.message.block_env.oracle + code = oracle.get_account(address).code if not is_valid_delegation(code): return False, address, code, Uint(0) @@ -141,7 +140,7 @@ def access_delegation( else: evm.accessed_addresses.add(address) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - code = get_account(state, address).code + code = oracle.get_account(address).code return True, address, code, access_gas_cost @@ -162,7 +161,7 @@ def set_delegation(message: Message) -> U256: refund_counter: `U256` Refund from authority which already exists in state. """ - state = message.block_env.state + oracle = message.block_env.oracle refund_counter = U256(0) for auth in message.tx_env.authorizations: if auth.chain_id not in (message.block_env.chain_id, U256(0)): @@ -178,7 +177,7 @@ def set_delegation(message: Message) -> U256: message.accessed_addresses.add(authority) - authority_account = get_account(state, authority) + authority_account = oracle.get_account(authority) authority_code = authority_account.code if authority_code and not is_valid_delegation(authority_code): @@ -188,20 +187,20 @@ def set_delegation(message: Message) -> U256: if authority_nonce != auth.nonce: continue - if account_exists(state, authority): + if oracle.account_exists(authority): refund_counter += U256(PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST) if auth.address == NULL_ADDRESS: code_to_set = b"" else: code_to_set = EOA_DELEGATION_MARKER + auth.address - set_code(state, authority, code_to_set) + oracle.set_code(authority, code_to_set) - increment_nonce(state, authority) + oracle.increment_nonce(authority) if message.code_address is None: raise InvalidBlock("Invalid type 4 transaction: no target") - message.code = get_account(state, message.code_address).code + message.code = oracle.get_account(message.code_address).code return refund_counter diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py index 226b3d3bb3..1b01f8d9dc 100644 --- a/src/ethereum/osaka/vm/instructions/environment.py +++ b/src/ethereum/osaka/vm/instructions/environment.py @@ -84,8 +84,9 @@ def balance(evm: Evm) -> None: charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) # OPERATION + oracle = evm.message.block_env.oracle # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.message.block_env.state, address).balance + balance = oracle.get_account(address).balance push(evm.stack, balance) @@ -351,7 +352,8 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.message.block_env.state, address).code + oracle = evm.message.block_env.oracle + code = oracle.get_account(address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -393,7 +395,8 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.message.block_env.state, address).code + oracle = evm.message.block_env.oracle + code = oracle.get_account(address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -479,7 +482,8 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.message.block_env.state, address) + oracle = evm.message.block_env.oracle + account = oracle.get_account(address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -510,10 +514,9 @@ def self_balance(evm: Evm) -> None: charge_gas(evm, GAS_FAST_STEP) # OPERATION + oracle = evm.message.block_env.oracle # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account( - evm.message.block_env.state, evm.message.current_target - ).balance + balance = oracle.get_account(evm.message.current_target).balance push(evm.stack, balance) diff --git a/src/ethereum/osaka/vm/instructions/memory.py b/src/ethereum/osaka/vm/instructions/memory.py index 89533af37e..f3c4013d27 100644 --- a/src/ethereum/osaka/vm/instructions/memory.py +++ b/src/ethereum/osaka/vm/instructions/memory.py @@ -11,6 +11,7 @@ Implementations of the EVM Memory instructions. """ + from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py index 65a0d5a9b6..80dfbceda9 100644 --- a/src/ethereum/osaka/vm/instructions/storage.py +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -11,13 +11,11 @@ Implementations of the EVM storage related instructions. """ -from ethereum_types.numeric import Uint + +from ethereum_types.numeric import U256, Uint from ...state import ( - get_storage, - get_storage_original, get_transient_storage, - set_storage, set_transient_storage, ) from .. import Evm @@ -56,9 +54,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage( - evm.message.block_env.state, evm.message.current_target, key - ) + oracle = evm.message.block_env.oracle + value_bytes = oracle.get_storage(evm.message.current_target, key) + value = U256.from_be_bytes(value_bytes) push(evm.stack, value) @@ -82,11 +80,13 @@ def sstore(evm: Evm) -> None: if evm.gas_left <= GAS_CALL_STIPEND: raise OutOfGasError - state = evm.message.block_env.state - original_value = get_storage_original( - state, evm.message.current_target, key + oracle = evm.message.block_env.oracle + original_value_bytes = oracle.get_storage_original( + evm.message.current_target, key ) - current_value = get_storage(state, evm.message.current_target, key) + original_value = U256.from_be_bytes(original_value_bytes) + current_value_bytes = oracle.get_storage(evm.message.current_target, key) + current_value = U256.from_be_bytes(current_value_bytes) gas_cost = Uint(0) @@ -126,7 +126,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(state, evm.message.current_target, key, new_value) + oracle.set_storage_value(evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/system.py b/src/ethereum/osaka/vm/instructions/system.py index d7308821bd..52d095c527 100644 --- a/src/ethereum/osaka/vm/instructions/system.py +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -91,7 +91,8 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.block_env.state, sender_address) + oracle = evm.message.block_env.oracle + sender = oracle.get_account(sender_address) if ( sender.balance < endowment @@ -104,16 +105,14 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.block_env.state, contract_address - ) or account_has_storage(evm.message.block_env.state, contract_address): - increment_nonce( - evm.message.block_env.state, evm.message.current_target - ) + if oracle.account_has_code_or_nonce( + contract_address + ) or oracle.account_has_storage(contract_address): + oracle.increment_nonce(evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.block_env.state, evm.message.current_target) + oracle.increment_nonce(evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -169,12 +168,11 @@ def create(evm: Evm) -> None: charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) # OPERATION + oracle = evm.message.block_env.oracle evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account( - evm.message.block_env.state, evm.message.current_target - ).nonce, + oracle.get_account(evm.message.current_target).nonce, ) generic_create( @@ -385,8 +383,9 @@ def call(evm: Evm) -> None: ) = access_delegation(evm, code_address) access_gas_cost += delegated_access_gas_cost + oracle = evm.message.block_env.oracle create_gas_cost = GAS_NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.block_env.state, to): + if value == 0 or oracle.is_account_alive(to): create_gas_cost = Uint(0) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE message_call_gas = calculate_message_call_gas( @@ -400,9 +399,7 @@ def call(evm: Evm) -> None: if evm.message.is_static and value != U256(0): raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.block_env.state, evm.message.current_target - ).balance + sender_balance = oracle.get_account(evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -483,10 +480,9 @@ def callcode(evm: Evm) -> None: charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION + oracle = evm.message.block_env.oracle evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.block_env.state, evm.message.current_target - ).balance + sender_balance = oracle.get_account(evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -531,13 +527,10 @@ def selfdestruct(evm: Evm) -> None: evm.accessed_addresses.add(beneficiary) gas_cost += GAS_COLD_ACCOUNT_ACCESS - if ( - not is_account_alive(evm.message.block_env.state, beneficiary) - and get_account( - evm.message.block_env.state, evm.message.current_target - ).balance - != 0 - ): + oracle = evm.message.block_env.oracle + current_balance = oracle.get_account(evm.message.current_target).balance + + if not oracle.is_account_alive(beneficiary) and current_balance != 0: gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) @@ -545,12 +538,9 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.block_env.state, originator - ).balance + originator_balance = oracle.get_account(originator).balance - move_ether( - evm.message.block_env.state, + oracle.move_ether( originator, beneficiary, originator_balance, @@ -558,10 +548,10 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion only if it was created # in the same transaction - if originator in evm.message.block_env.state.created_accounts: + if oracle.is_created_account(originator): # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.block_env.state, originator, U256(0)) + oracle.set_account_balance(originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py index 18225616d6..db02138a53 100644 --- a/src/ethereum/osaka/vm/interpreter.py +++ b/src/ethereum/osaka/vm/interpreter.py @@ -108,9 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - block_env.state, message.current_target - ) or account_has_storage(block_env.state, message.current_target) + is_collision = block_env.oracle.account_has_code_or_nonce( + message.current_target + ) or block_env.oracle.account_has_storage(message.current_target) if is_collision: return MessageCallOutput( Uint(0), @@ -130,7 +130,7 @@ def process_message_call(message: Message) -> MessageCallOutput: if delegated_address is not None: message.disable_precompiles = True message.accessed_addresses.add(delegated_address) - message.code = get_account(block_env.state, delegated_address).code + message.code = block_env.oracle.get_account(delegated_address).code message.code_address = delegated_address evm = process_message(message) diff --git a/src/ethereum/state_oracle/__init__.py b/src/ethereum/state_oracle/__init__.py new file mode 100644 index 0000000000..158a1cb7ac --- /dev/null +++ b/src/ethereum/state_oracle/__init__.py @@ -0,0 +1,14 @@ +""" +State Oracle Interface for Ethereum Execution Specs. + +Provides an abstract interface for state access that can be implemented +by different state management strategies. +""" + +from .interface import MerkleOracle +from .memory_oracle import MemoryMerkleOracle + +__all__ = [ + "MerkleOracle", + "MemoryMerkleOracle", +] diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py new file mode 100644 index 0000000000..b7ade0fae6 --- /dev/null +++ b/src/ethereum/state_oracle/interface.py @@ -0,0 +1,213 @@ +""" +Abstract interface for state oracle implementations. +""" + +from typing import Any, Callable, Dict, Optional, Protocol, Tuple + +from ethereum_types.bytes import Bytes20, Bytes32 + +# Use generic types for compatibility across forks +Account = Any +Address = Bytes20 + + +class MerkleOracle(Protocol): + """ + Oracle interface for Merkle Patricia Trie based state. + """ + + def get_account(self, address: Address) -> Account: + """ + Get account information for the given address. + + Returns EMPTY_ACCOUNT if the account doesn't exist. + """ + ... + + def get_account_optional(self, address: Address) -> Optional[Account]: + """ + Get account information for the given address. + + Returns None if the account doesn't exist. + Use this when you need to distinguish between non-existent and empty accounts. + """ + ... + + def get_storage(self, address: Address, key: Bytes32) -> Bytes32: + """Get storage value at key for the given address.""" + ... + + def write( + self, + accounts: Dict[Address, Optional[Account]], + storage: Dict[Tuple[Address, Bytes32], Bytes32], + ) -> None: + """ + Write account and storage changes to state. + + Parameters + ---------- + accounts : Dict[Address, Optional[Account]] + Account changes. None values indicate account deletion. + storage : Dict[Tuple[Address, Bytes32], Bytes32] + Storage changes as (address, key) -> value mapping. + """ + ... + + def state_root(self) -> Bytes32: + """Compute and return the current state root.""" + ... + + # Extensions needed for complete EVM instruction support + def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: + """ + Get original storage value before current transaction started. + + This is required for SSTORE gas calculations per EIP-2200. + The implementation should use state snapshots/checkpoints to track + pre-transaction values. + TODO: The oracle does not have a `begin_transaction` method, so it kind of breaks here. + + Parameters + ---------- + address : Bytes20 + Contract address + key : Bytes32 + Storage slot key + + Returns + ------- + Bytes32 + Original storage value as 32-byte value + """ + ... + + def set_storage_value( + self, address: Address, key: Bytes32, value: Any + ) -> None: + """ + Set individual storage value. + + Parameters + ---------- + address : Bytes20 + Contract address + key : Bytes32 + Storage slot key + value : Any + Storage value (U256 or Bytes32) + """ + ... + + def account_has_code_or_nonce(self, address: Address) -> bool: + """ + Check if account has non-zero code or nonce. + + Used during contract creation to check if address is available. + """ + ... + + def account_has_storage(self, address: Address) -> bool: + """ + Check if account has any storage slots. + + Used during contract creation to check if address is available. + """ + ... + + def is_account_alive(self, address: Address) -> bool: + """ + Check if account is alive (exists and not marked for deletion). + + Used in CALL instructions and SELFDESTRUCT. + """ + ... + + def account_exists(self, address: Address) -> bool: + """ + Check if account exists in the state. + """ + ... + + def increment_nonce(self, address: Address) -> None: + """ + Increment account nonce. + + Used during contract creation and transaction processing. + """ + ... + + def set_code(self, address: Address, code: Any) -> None: + """ + Set account code. + + Used during contract creation and EOA delegation. + """ + ... + + def set_account_balance(self, address: Address, balance: Any) -> None: + """ + Set account balance. + + Used in SELFDESTRUCT and other balance transfer operations. + """ + ... + + def move_ether( + self, sender: Bytes20, recipient: Bytes20, amount: Any + ) -> None: + """ + Transfer ether between accounts. + + Handles balance updates for both sender and recipient accounts. + Used in CALL instructions and contract transfers. + """ + ... + + def add_created_account(self, address: Address) -> None: + """ + Mark account as created in current transaction. + + Used for tracking accounts created during transaction execution. + """ + ... + + def is_created_account(self, address: Address) -> bool: + """ + Check if account was created in current transaction. + + Used in SELFDESTRUCT and other operations that need to know + if account was created in current transaction. + """ + ... + + def account_exists_and_is_empty(self, address: Address) -> bool: + """ + Check if account exists and is empty. + + Used for account cleanup logic. + """ + ... + + def destroy_account(self, address: Address) -> None: + """ + Mark account for destruction. + + Used in SELFDESTRUCT and account cleanup. + """ + ... + + def modify_state( + self, address: Address, modifier_func: Callable[[Account], None] + ) -> None: + """ + Modify an account using a modifier function. + + Parameters + ---------- + address : Address + Address of account to modify + modifier_func : Callable[[Account], None] + Function that takes an account and modifies it in place + """ + ... diff --git a/src/ethereum/state_oracle/memory_oracle.py b/src/ethereum/state_oracle/memory_oracle.py new file mode 100644 index 0000000000..299046d8c1 --- /dev/null +++ b/src/ethereum/state_oracle/memory_oracle.py @@ -0,0 +1,191 @@ +""" +Memory-based state oracle implementation. + +This impl wraps the existing execution-specs `State` object +to provide the oracle interface. +This is mainly done as a first pass to make the diff small. +""" + +from typing import Any, Dict, Optional, Tuple + +from ethereum_types.bytes import Bytes20, Bytes32 +from ethereum_types.numeric import Uint + +# TODO: This file is current Osaka specific -- we could move state.py into here to mitigate this. + +# Use generic types for compatibility across forks +Account = Any +Address = Bytes20 + +from .interface import MerkleOracle + + +class MemoryMerkleOracle: + """ + Merkle oracle implementation that wraps existing execution-specs state. + """ + + def __init__(self, state): + """ + Initialize oracle with existing state. + + Parameters + ---------- + state : State + The existing execution-specs state object. + """ + self._state = state + + def get_account(self, address: Address) -> Account: + """Get account information for the given address. Returns EMPTY_ACCOUNT if not exists.""" + from ethereum.osaka.state import get_account + + return get_account(self._state, address) + + def get_account_optional(self, address: Address) -> Optional[Account]: + """Get account information for the given address. Returns None if not exists.""" + from ethereum.osaka.state import get_account_optional + + return get_account_optional(self._state, address) + + def get_storage(self, address: Address, key: Bytes32) -> Bytes32: + """Get storage value at key for the given address.""" + from ethereum_types.numeric import U256 + + from ethereum.osaka.state import get_storage + + # Get storage as U256 and convert to Bytes32 + storage_value = get_storage(self._state, address, key) + return storage_value.to_be_bytes32() + + def write( + self, + accounts: Dict[Bytes20, Optional[Any]], + storage: Dict[Tuple[Bytes20, Bytes32], Bytes32], + ) -> None: + """Write account and storage changes to state.""" + from ethereum_types.numeric import U256 + + from ethereum.osaka.state import ( + destroy_account, + set_account, + set_storage, + ) + + # Write account changes + for address, account in accounts.items(): + if account is None: + destroy_account(self._state, address) + else: + set_account(self._state, address, account) + + for (address, key), value in storage.items(): + storage_value = U256.from_be_bytes(value) + set_storage(self._state, address, key, storage_value) + + def state_root(self) -> Bytes32: + """Compute and return the current state root.""" + from ethereum.osaka.state import state_root + + return state_root(self._state) + + def get_storage_original(self, address, key): + """Get original storage value (before transaction started).""" + from ethereum_types.numeric import U256 + + from ethereum.osaka.state import get_storage_original + + storage_value = get_storage_original(self._state, address, key) + return storage_value.to_be_bytes32() + + def set_storage_value(self, address, key, value): + """Set a single storage value.""" + from ethereum_types.numeric import U256 + + from ethereum.osaka.state import set_storage + + if isinstance(value, bytes): + storage_value = U256.from_be_bytes(value) + else: + storage_value = value + set_storage(self._state, address, key, storage_value) + + def account_has_code_or_nonce(self, address): + """Check if account has non-zero code or nonce.""" + from ethereum.osaka.state import account_has_code_or_nonce + + return account_has_code_or_nonce(self._state, address) + + def account_has_storage(self, address): + """Check if account has any storage slots.""" + from ethereum.osaka.state import account_has_storage + + return account_has_storage(self._state, address) + + def is_account_alive(self, address): + """Check if account is alive (exists and not marked for deletion).""" + from ethereum.osaka.state import is_account_alive + + return is_account_alive(self._state, address) + + def account_exists(self, address): + """Check if account exists in the state.""" + from ethereum.osaka.state import account_exists + + return account_exists(self._state, address) + + def increment_nonce(self, address): + """Increment account nonce.""" + from ethereum.osaka.state import increment_nonce + + increment_nonce(self._state, address) + + def set_code(self, address, code): + """Set account code.""" + from ethereum.osaka.state import set_code + + set_code(self._state, address, code) + + def set_account_balance(self, address, balance): + """Set account balance.""" + from ethereum.osaka.state import set_account_balance + + set_account_balance(self._state, address, balance) + + def move_ether(self, sender, recipient, amount): + """Transfer ether between accounts.""" + from ethereum.osaka.state import move_ether + + move_ether(self._state, sender, recipient, amount) + + def add_created_account(self, address): + """Mark account as created in current transaction.""" + # Add to the created_accounts set in state + self._state.created_accounts.add(address) + + def is_created_account(self, address): + """Check if account was created in current transaction.""" + return address in self._state.created_accounts + + def account_exists_and_is_empty(self, address): + """Check if account exists and is empty.""" + from ethereum.osaka.state import account_exists_and_is_empty + + return account_exists_and_is_empty(self._state, address) + + def destroy_account(self, address): + """Mark account for destruction.""" + from ethereum.osaka.state import destroy_account + + destroy_account(self._state, address) + + def modify_state(self, address, modifier_func): + """Modify an account using a modifier function.""" + from ethereum.osaka.state import modify_state + + modify_state(self._state, address, modifier_func) + + @property + def state(self): + """Access to underlying state for compatibility.""" + return self._state From c214f9f5c1ef3dd0a14649e6dd0eabd22be074a3 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 18:42:25 +0100 Subject: [PATCH 02/11] revert format --- src/ethereum/osaka/vm/eoa_delegation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py index 28b3743224..2c2aecb0ff 100644 --- a/src/ethereum/osaka/vm/eoa_delegation.py +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -19,7 +19,7 @@ from . import Evm, Message SET_CODE_TX_MAGIC = b"\x05" -EOA_DELEGATION_MARKER = b"\xef\x01\x00" +EOA_DELEGATION_MARKER = b"\xEF\x01\x00" EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) EOA_DELEGATED_CODE_LENGTH = 23 PER_EMPTY_ACCOUNT_COST = 25000 From 4db099ed9a1a8fc256cb56cd19c6f145783285e8 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 18:46:28 +0100 Subject: [PATCH 03/11] remove write --- src/ethereum/state_oracle/interface.py | 18 +-------------- src/ethereum/state_oracle/memory_oracle.py | 27 +--------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index b7ade0fae6..6e727711ad 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -2,7 +2,7 @@ Abstract interface for state oracle implementations. """ -from typing import Any, Callable, Dict, Optional, Protocol, Tuple +from typing import Any, Callable, Optional, Protocol from ethereum_types.bytes import Bytes20, Bytes32 @@ -37,22 +37,6 @@ def get_storage(self, address: Address, key: Bytes32) -> Bytes32: """Get storage value at key for the given address.""" ... - def write( - self, - accounts: Dict[Address, Optional[Account]], - storage: Dict[Tuple[Address, Bytes32], Bytes32], - ) -> None: - """ - Write account and storage changes to state. - - Parameters - ---------- - accounts : Dict[Address, Optional[Account]] - Account changes. None values indicate account deletion. - storage : Dict[Tuple[Address, Bytes32], Bytes32] - Storage changes as (address, key) -> value mapping. - """ - ... def state_root(self) -> Bytes32: """Compute and return the current state root.""" diff --git a/src/ethereum/state_oracle/memory_oracle.py b/src/ethereum/state_oracle/memory_oracle.py index 299046d8c1..c772124f31 100644 --- a/src/ethereum/state_oracle/memory_oracle.py +++ b/src/ethereum/state_oracle/memory_oracle.py @@ -6,10 +6,9 @@ This is mainly done as a first pass to make the diff small. """ -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional from ethereum_types.bytes import Bytes20, Bytes32 -from ethereum_types.numeric import Uint # TODO: This file is current Osaka specific -- we could move state.py into here to mitigate this. @@ -58,30 +57,6 @@ def get_storage(self, address: Address, key: Bytes32) -> Bytes32: storage_value = get_storage(self._state, address, key) return storage_value.to_be_bytes32() - def write( - self, - accounts: Dict[Bytes20, Optional[Any]], - storage: Dict[Tuple[Bytes20, Bytes32], Bytes32], - ) -> None: - """Write account and storage changes to state.""" - from ethereum_types.numeric import U256 - - from ethereum.osaka.state import ( - destroy_account, - set_account, - set_storage, - ) - - # Write account changes - for address, account in accounts.items(): - if account is None: - destroy_account(self._state, address) - else: - set_account(self._state, address, account) - - for (address, key), value in storage.items(): - storage_value = U256.from_be_bytes(value) - set_storage(self._state, address, key, storage_value) def state_root(self) -> Bytes32: """Compute and return the current state root.""" From 3112a1c6ca9c9cf070c6705b297fc67026c217e6 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 18:56:35 +0100 Subject: [PATCH 04/11] format --- src/ethereum/osaka/fork.py | 5 +---- src/ethereum/osaka/vm/instructions/storage.py | 5 +---- src/ethereum/state_oracle/interface.py | 8 ++++---- src/ethereum/state_oracle/memory_oracle.py | 10 ++++------ 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index e6e249df1c..877a0243ed 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -51,10 +51,7 @@ compute_requests_hash, parse_deposit_requests, ) -from .state import ( - State, - TransientStorage, -) +from .state import State, TransientStorage from .transactions import ( AccessListTransaction, BlobTransaction, diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py index 80dfbceda9..eb64a13c2f 100644 --- a/src/ethereum/osaka/vm/instructions/storage.py +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -14,10 +14,7 @@ from ethereum_types.numeric import U256, Uint -from ...state import ( - get_transient_storage, - set_transient_storage, -) +from ...state import get_transient_storage, set_transient_storage from .. import Evm from ..exceptions import OutOfGasError, WriteInStaticContext from ..gas import ( diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index 6e727711ad..26f4257a05 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -29,7 +29,8 @@ def get_account_optional(self, address: Address) -> Optional[Account]: Get account information for the given address. Returns None if the account doesn't exist. - Use this when you need to distinguish between non-existent and empty accounts. + Use this when you need to distinguish between non-existent and + empty accounts. """ ... @@ -37,7 +38,6 @@ def get_storage(self, address: Address, key: Bytes32) -> Bytes32: """Get storage value at key for the given address.""" ... - def state_root(self) -> Bytes32: """Compute and return the current state root.""" ... @@ -48,8 +48,8 @@ def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: Get original storage value before current transaction started. This is required for SSTORE gas calculations per EIP-2200. - The implementation should use state snapshots/checkpoints to track - pre-transaction values. + The implementation should use state snapshots/checkpoints to + track pre-transaction values. TODO: The oracle does not have a `begin_transaction` method, so it kind of breaks here. Parameters diff --git a/src/ethereum/state_oracle/memory_oracle.py b/src/ethereum/state_oracle/memory_oracle.py index c772124f31..e87652bb46 100644 --- a/src/ethereum/state_oracle/memory_oracle.py +++ b/src/ethereum/state_oracle/memory_oracle.py @@ -2,22 +2,21 @@ Memory-based state oracle implementation. This impl wraps the existing execution-specs `State` object -to provide the oracle interface. -This is mainly done as a first pass to make the diff small. +to provide the oracle interface. +This is mainly done as a first pass to make the diff small. """ from typing import Any, Optional from ethereum_types.bytes import Bytes20, Bytes32 -# TODO: This file is current Osaka specific -- we could move state.py into here to mitigate this. +# TODO: This file is current Osaka specific -- we could move state.py +# into here to mitigate this. # Use generic types for compatibility across forks Account = Any Address = Bytes20 -from .interface import MerkleOracle - class MemoryMerkleOracle: """ @@ -57,7 +56,6 @@ def get_storage(self, address: Address, key: Bytes32) -> Bytes32: storage_value = get_storage(self._state, address, key) return storage_value.to_be_bytes32() - def state_root(self) -> Bytes32: """Compute and return the current state root.""" from ethereum.osaka.state import state_root From a340007da7d2052adb1f7a52ff47ec8a11bd0a54 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 19:02:49 +0100 Subject: [PATCH 05/11] formatting --- src/ethereum/osaka/utils/message.py | 1 - src/ethereum/osaka/vm/eoa_delegation.py | 1 - .../osaka/vm/instructions/environment.py | 1 - src/ethereum/osaka/vm/instructions/system.py | 9 -------- src/ethereum/osaka/vm/interpreter.py | 3 --- src/ethereum/state_oracle/interface.py | 7 ++++--- src/ethereum/state_oracle/memory_oracle.py | 21 +++++++++++-------- 7 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py index ca32f4309a..ed8dfec6a3 100644 --- a/src/ethereum/osaka/utils/message.py +++ b/src/ethereum/osaka/utils/message.py @@ -17,7 +17,6 @@ from ethereum_types.numeric import Uint from ..fork_types import Address -from ..state import get_account from ..transactions import Transaction from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py index 2c2aecb0ff..256b8184cd 100644 --- a/src/ethereum/osaka/vm/eoa_delegation.py +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -13,7 +13,6 @@ from ethereum.exceptions import InvalidBlock, InvalidSignatureError from ..fork_types import Address, Authorization -from ..state import account_exists, get_account, increment_nonce, set_code from ..utils.hexadecimal import hex_to_address from ..vm.gas import GAS_COLD_ACCOUNT_ACCESS, GAS_WARM_ACCESS from . import Evm, Message diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py index 1b01f8d9dc..9f15d07bab 100644 --- a/src/ethereum/osaka/vm/instructions/environment.py +++ b/src/ethereum/osaka/vm/instructions/environment.py @@ -19,7 +19,6 @@ from ethereum.utils.numeric import ceil32 from ...fork_types import EMPTY_ACCOUNT -from ...state import get_account from ...utils.address import to_address_masked from ...vm.memory import buffer_read, memory_write from .. import Evm diff --git a/src/ethereum/osaka/vm/instructions/system.py b/src/ethereum/osaka/vm/instructions/system.py index 52d095c527..1f0c5fa225 100644 --- a/src/ethereum/osaka/vm/instructions/system.py +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -18,15 +18,6 @@ from ethereum.utils.numeric import ceil32 from ...fork_types import Address -from ...state import ( - account_has_code_or_nonce, - account_has_storage, - get_account, - increment_nonce, - is_account_alive, - move_ether, - set_account_balance, -) from ...utils.address import ( compute_contract_address, compute_create2_contract_address, diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py index db02138a53..ea4b1331f7 100644 --- a/src/ethereum/osaka/vm/interpreter.py +++ b/src/ethereum/osaka/vm/interpreter.py @@ -33,12 +33,9 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_has_code_or_nonce, - account_has_storage, begin_transaction, commit_transaction, destroy_storage, - get_account, increment_nonce, mark_account_created, move_ether, diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index 26f4257a05..29d49c6d1e 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -50,7 +50,8 @@ def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: This is required for SSTORE gas calculations per EIP-2200. The implementation should use state snapshots/checkpoints to track pre-transaction values. - TODO: The oracle does not have a `begin_transaction` method, so it kind of breaks here. + TODO: The oracle does not have a `begin_transaction` method, + so it kind of breaks here. Parameters ---------- @@ -182,7 +183,7 @@ def destroy_account(self, address: Address) -> None: ... def modify_state( - self, address: Address, modifier_func: Callable[[Account], None] + self, address: Address, modifier_function: Callable[[Account], None] ) -> None: """ Modify an account using a modifier function. @@ -191,7 +192,7 @@ def modify_state( ---------- address : Address Address of account to modify - modifier_func : Callable[[Account], None] + modifier_function : Callable[[Account], None] Function that takes an account and modifies it in place """ ... diff --git a/src/ethereum/state_oracle/memory_oracle.py b/src/ethereum/state_oracle/memory_oracle.py index e87652bb46..144fd398e9 100644 --- a/src/ethereum/state_oracle/memory_oracle.py +++ b/src/ethereum/state_oracle/memory_oracle.py @@ -35,24 +35,29 @@ def __init__(self, state): self._state = state def get_account(self, address: Address) -> Account: - """Get account information for the given address. Returns EMPTY_ACCOUNT if not exists.""" + """ + Get account information for the given address. + + Returns EMPTY_ACCOUNT if not exists. + """ from ethereum.osaka.state import get_account return get_account(self._state, address) def get_account_optional(self, address: Address) -> Optional[Account]: - """Get account information for the given address. Returns None if not exists.""" + """ + Get account information for the given address. + + Returns None if not exists. + """ from ethereum.osaka.state import get_account_optional return get_account_optional(self._state, address) def get_storage(self, address: Address, key: Bytes32) -> Bytes32: """Get storage value at key for the given address.""" - from ethereum_types.numeric import U256 - from ethereum.osaka.state import get_storage - # Get storage as U256 and convert to Bytes32 storage_value = get_storage(self._state, address, key) return storage_value.to_be_bytes32() @@ -64,8 +69,6 @@ def state_root(self) -> Bytes32: def get_storage_original(self, address, key): """Get original storage value (before transaction started).""" - from ethereum_types.numeric import U256 - from ethereum.osaka.state import get_storage_original storage_value = get_storage_original(self._state, address, key) @@ -152,11 +155,11 @@ def destroy_account(self, address): destroy_account(self._state, address) - def modify_state(self, address, modifier_func): + def modify_state(self, address, modifier_function): """Modify an account using a modifier function.""" from ethereum.osaka.state import modify_state - modify_state(self._state, address, modifier_func) + modify_state(self._state, address, modifier_function) @property def state(self): From ed85d8f1f455a30709a8d1b2e6d51b5591b354ff Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 19:17:45 +0100 Subject: [PATCH 06/11] python format --- src/ethereum/state_oracle/interface.py | 30 +++++++++----------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index 29d49c6d1e..fab53a088a 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -20,33 +20,35 @@ def get_account(self, address: Address) -> Account: """ Get account information for the given address. + `address` is the account address to retrieve. + Returns EMPTY_ACCOUNT if the account doesn't exist. """ - ... def get_account_optional(self, address: Address) -> Optional[Account]: """ Get account information for the given address. + `address` is the account address to retrieve. + Returns None if the account doesn't exist. Use this when you need to distinguish between non-existent and empty accounts. """ - ... def get_storage(self, address: Address, key: Bytes32) -> Bytes32: - """Get storage value at key for the given address.""" - ... + """Get storage value at `key` for the given `address`.""" def state_root(self) -> Bytes32: """Compute and return the current state root.""" - ... # Extensions needed for complete EVM instruction support def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: """ Get original storage value before current transaction started. + `address` is the contract address and `key` is the storage slot. + This is required for SSTORE gas calculations per EIP-2200. The implementation should use state snapshots/checkpoints to track pre-transaction values. @@ -65,7 +67,6 @@ def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: Bytes32 Original storage value as 32-byte value """ - ... def set_storage_value( self, address: Address, key: Bytes32, value: Any @@ -73,6 +74,9 @@ def set_storage_value( """ Set individual storage value. + `address` is the contract address, `key` is the storage slot, + and `value` is the new storage value. + Parameters ---------- address : Bytes20 @@ -82,7 +86,6 @@ def set_storage_value( value : Any Storage value (U256 or Bytes32) """ - ... def account_has_code_or_nonce(self, address: Address) -> bool: """ @@ -90,7 +93,6 @@ def account_has_code_or_nonce(self, address: Address) -> bool: Used during contract creation to check if address is available. """ - ... def account_has_storage(self, address: Address) -> bool: """ @@ -98,7 +100,6 @@ def account_has_storage(self, address: Address) -> bool: Used during contract creation to check if address is available. """ - ... def is_account_alive(self, address: Address) -> bool: """ @@ -106,13 +107,11 @@ def is_account_alive(self, address: Address) -> bool: Used in CALL instructions and SELFDESTRUCT. """ - ... def account_exists(self, address: Address) -> bool: """ Check if account exists in the state. """ - ... def increment_nonce(self, address: Address) -> None: """ @@ -120,7 +119,6 @@ def increment_nonce(self, address: Address) -> None: Used during contract creation and transaction processing. """ - ... def set_code(self, address: Address, code: Any) -> None: """ @@ -128,7 +126,6 @@ def set_code(self, address: Address, code: Any) -> None: Used during contract creation and EOA delegation. """ - ... def set_account_balance(self, address: Address, balance: Any) -> None: """ @@ -136,7 +133,6 @@ def set_account_balance(self, address: Address, balance: Any) -> None: Used in SELFDESTRUCT and other balance transfer operations. """ - ... def move_ether( self, sender: Bytes20, recipient: Bytes20, amount: Any @@ -147,7 +143,6 @@ def move_ether( Handles balance updates for both sender and recipient accounts. Used in CALL instructions and contract transfers. """ - ... def add_created_account(self, address: Address) -> None: """ @@ -155,7 +150,6 @@ def add_created_account(self, address: Address) -> None: Used for tracking accounts created during transaction execution. """ - ... def is_created_account(self, address: Address) -> bool: """ @@ -164,7 +158,6 @@ def is_created_account(self, address: Address) -> bool: Used in SELFDESTRUCT and other operations that need to know if account was created in current transaction. """ - ... def account_exists_and_is_empty(self, address: Address) -> bool: """ @@ -172,7 +165,6 @@ def account_exists_and_is_empty(self, address: Address) -> bool: Used for account cleanup logic. """ - ... def destroy_account(self, address: Address) -> None: """ @@ -180,7 +172,6 @@ def destroy_account(self, address: Address) -> None: Used in SELFDESTRUCT and account cleanup. """ - ... def modify_state( self, address: Address, modifier_function: Callable[[Account], None] @@ -195,4 +186,3 @@ def modify_state( modifier_function : Callable[[Account], None] Function that takes an account and modifies it in place """ - ... From ad9dd181b594986f84a73214a058866ee7934748 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 19:18:22 +0100 Subject: [PATCH 07/11] python lint --- src/ethereum/state_oracle/interface.py | 57 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index fab53a088a..0005b06c91 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -16,7 +16,9 @@ class MerkleOracle(Protocol): Oracle interface for Merkle Patricia Trie based state. """ - def get_account(self, address: Address) -> Account: + def get_account( + self, address: Address # noqa: U100 + ) -> Account: # noqa: U100 """ Get account information for the given address. @@ -25,7 +27,9 @@ def get_account(self, address: Address) -> Account: Returns EMPTY_ACCOUNT if the account doesn't exist. """ - def get_account_optional(self, address: Address) -> Optional[Account]: + def get_account_optional( + self, address: Address # noqa: U100 + ) -> Optional[Account]: # noqa: U100 """ Get account information for the given address. @@ -36,14 +40,17 @@ def get_account_optional(self, address: Address) -> Optional[Account]: empty accounts. """ - def get_storage(self, address: Address, key: Bytes32) -> Bytes32: + def get_storage( + self, address: Address, key: Bytes32 # noqa: U100 + ) -> Bytes32: # noqa: U100 """Get storage value at `key` for the given `address`.""" - def state_root(self) -> Bytes32: + def state_root(self) -> Bytes32: # noqa: U100 """Compute and return the current state root.""" - # Extensions needed for complete EVM instruction support - def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: + def get_storage_original( + self, address: Address, key: Bytes32 # noqa: U100 + ) -> Bytes32: # noqa: U100 """ Get original storage value before current transaction started. @@ -53,7 +60,7 @@ def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: The implementation should use state snapshots/checkpoints to track pre-transaction values. TODO: The oracle does not have a `begin_transaction` method, - so it kind of breaks here. + TODO: so it kind of breaks here. Parameters ---------- @@ -69,7 +76,7 @@ def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: """ def set_storage_value( - self, address: Address, key: Bytes32, value: Any + self, address: Address, key: Bytes32, value: Any # noqa: U100 ) -> None: """ Set individual storage value. @@ -87,47 +94,51 @@ def set_storage_value( Storage value (U256 or Bytes32) """ - def account_has_code_or_nonce(self, address: Address) -> bool: + def account_has_code_or_nonce( + self, address: Address # noqa: U100 + ) -> bool: # noqa: U100 """ Check if account has non-zero code or nonce. Used during contract creation to check if address is available. """ - def account_has_storage(self, address: Address) -> bool: + def account_has_storage(self, address: Address) -> bool: # noqa: U100 """ Check if account has any storage slots. Used during contract creation to check if address is available. """ - def is_account_alive(self, address: Address) -> bool: + def is_account_alive(self, address: Address) -> bool: # noqa: U100 """ Check if account is alive (exists and not marked for deletion). Used in CALL instructions and SELFDESTRUCT. """ - def account_exists(self, address: Address) -> bool: + def account_exists(self, address: Address) -> bool: # noqa: U100 """ Check if account exists in the state. """ - def increment_nonce(self, address: Address) -> None: + def increment_nonce(self, address: Address) -> None: # noqa: U100 """ Increment account nonce. Used during contract creation and transaction processing. """ - def set_code(self, address: Address, code: Any) -> None: + def set_code(self, address: Address, code: Any) -> None: # noqa: U100 """ Set account code. Used during contract creation and EOA delegation. """ - def set_account_balance(self, address: Address, balance: Any) -> None: + def set_account_balance( + self, address: Address, balance: Any # noqa: U100 + ) -> None: # noqa: U100 """ Set account balance. @@ -135,7 +146,7 @@ def set_account_balance(self, address: Address, balance: Any) -> None: """ def move_ether( - self, sender: Bytes20, recipient: Bytes20, amount: Any + self, sender: Bytes20, recipient: Bytes20, amount: Any # noqa: U100 ) -> None: """ Transfer ether between accounts. @@ -144,14 +155,14 @@ def move_ether( Used in CALL instructions and contract transfers. """ - def add_created_account(self, address: Address) -> None: + def add_created_account(self, address: Address) -> None: # noqa: U100 """ Mark account as created in current transaction. Used for tracking accounts created during transaction execution. """ - def is_created_account(self, address: Address) -> bool: + def is_created_account(self, address: Address) -> bool: # noqa: U100 """ Check if account was created in current transaction. @@ -159,14 +170,16 @@ def is_created_account(self, address: Address) -> bool: if account was created in current transaction. """ - def account_exists_and_is_empty(self, address: Address) -> bool: + def account_exists_and_is_empty( + self, address: Address # noqa: U100 + ) -> bool: # noqa: U100 """ Check if account exists and is empty. Used for account cleanup logic. """ - def destroy_account(self, address: Address) -> None: + def destroy_account(self, address: Address) -> None: # noqa: U100 """ Mark account for destruction. @@ -174,7 +187,9 @@ def destroy_account(self, address: Address) -> None: """ def modify_state( - self, address: Address, modifier_function: Callable[[Account], None] + self, + address: Address, # noqa: U100 + modifier_function: Callable[[Account], None], # noqa: U100 ) -> None: """ Modify an account using a modifier function. From d12c5d461b85cdf4af73f222df2c24e098a4cb9e Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 19:37:09 +0100 Subject: [PATCH 08/11] python lint --- src/ethereum/osaka/fork.py | 6 +- src/ethereum/osaka/vm/instructions/storage.py | 4 +- src/ethereum/osaka/vm/interpreter.py | 29 ++++------ src/ethereum/state_oracle/interface.py | 20 +++++-- src/ethereum/state_oracle/memory_oracle.py | 56 +++++++++++-------- 5 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index 877a0243ed..e8db50f836 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -13,7 +13,7 @@ """ from dataclasses import dataclass -from typing import List, Optional, Tuple +from typing import Any, List, Optional, Tuple from ethereum_rlp import rlp from ethereum_types.bytes import Bytes @@ -179,7 +179,9 @@ def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: return recent_block_hashes -def state_transition(chain: BlockChain, block: Block, oracle=None) -> None: +def state_transition( + chain: BlockChain, block: Block, oracle: Optional[Any] = None +) -> None: """ Attempts to apply a block to an existing block chain. diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py index eb64a13c2f..a018b8d301 100644 --- a/src/ethereum/osaka/vm/instructions/storage.py +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -123,7 +123,9 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - oracle.set_storage_value(evm.message.current_target, key, new_value) + oracle.set_storage_value( + evm.message.current_target, key, new_value.to_be_bytes32() + ) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py index ea4b1331f7..eb79f51601 100644 --- a/src/ethereum/osaka/vm/interpreter.py +++ b/src/ethereum/osaka/vm/interpreter.py @@ -32,16 +32,7 @@ from ..blocks import Log from ..fork_types import Address -from ..state import ( - begin_transaction, - commit_transaction, - destroy_storage, - increment_nonce, - mark_account_created, - move_ether, - rollback_transaction, - set_code, -) +from ..state import begin_transaction, commit_transaction, rollback_transaction from ..vm import Message from ..vm.eoa_delegation import get_delegated_code_address, set_delegation from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -169,7 +160,7 @@ def process_create_message(message: Message) -> Evm: evm: :py:class:`~ethereum.osaka.vm.Evm` Items containing execution specific objects. """ - state = message.block_env.state + state = message.block_env.oracle.state transient_storage = message.tx_env.transient_storage # take snapshot of state before processing the message begin_transaction(state, transient_storage) @@ -181,15 +172,15 @@ def process_create_message(message: Message) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(state, message.current_target) + message.block_env.oracle.destroy_storage(message.current_target) # In the previously mentioned edge case the preexisting storage is ignored # for gas refund purposes. In order to do this we must track created # accounts. This tracking is also needed to respect the constraints # added to SELFDESTRUCT by EIP-6780. - mark_account_created(state, message.current_target) + message.block_env.oracle.add_created_account(message.current_target) - increment_nonce(state, message.current_target) + message.block_env.oracle.increment_nonce(message.current_target) evm = process_message(message) if not evm.error: contract_code = evm.output @@ -207,7 +198,9 @@ def process_create_message(message: Message) -> Evm: evm.output = b"" evm.error = error else: - set_code(state, message.current_target, contract_code) + message.block_env.oracle.set_code( + message.current_target, contract_code + ) commit_transaction(state, transient_storage) else: rollback_transaction(state, transient_storage) @@ -228,7 +221,7 @@ def process_message(message: Message) -> Evm: evm: :py:class:`~ethereum.osaka.vm.Evm` Items containing execution specific objects """ - state = message.block_env.state + state = message.block_env.oracle.state transient_storage = message.tx_env.transient_storage if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") @@ -237,8 +230,8 @@ def process_message(message: Message) -> Evm: begin_transaction(state, transient_storage) if message.should_transfer_value and message.value != 0: - move_ether( - state, message.caller, message.current_target, message.value + message.block_env.oracle.move_ether( + message.caller, message.current_target, message.value ) evm = execute_code(message) diff --git a/src/ethereum/state_oracle/interface.py b/src/ethereum/state_oracle/interface.py index 0005b06c91..bcc03a2803 100644 --- a/src/ethereum/state_oracle/interface.py +++ b/src/ethereum/state_oracle/interface.py @@ -5,6 +5,7 @@ from typing import Any, Callable, Optional, Protocol from ethereum_types.bytes import Bytes20, Bytes32 +from ethereum_types.numeric import U256 # Use generic types for compatibility across forks Account = Any @@ -76,7 +77,7 @@ def get_storage_original( """ def set_storage_value( - self, address: Address, key: Bytes32, value: Any # noqa: U100 + self, address: Address, key: Bytes32, value: Bytes32 # noqa: U100 ) -> None: """ Set individual storage value. @@ -90,8 +91,8 @@ def set_storage_value( Contract address key : Bytes32 Storage slot key - value : Any - Storage value (U256 or Bytes32) + value : Bytes32 + Storage value as 32-byte value """ def account_has_code_or_nonce( @@ -137,7 +138,7 @@ def set_code(self, address: Address, code: Any) -> None: # noqa: U100 """ def set_account_balance( - self, address: Address, balance: Any # noqa: U100 + self, address: Address, balance: U256 # noqa: U100 ) -> None: # noqa: U100 """ Set account balance. @@ -146,7 +147,7 @@ def set_account_balance( """ def move_ether( - self, sender: Bytes20, recipient: Bytes20, amount: Any # noqa: U100 + self, sender: Bytes20, recipient: Bytes20, amount: U256 # noqa: U100 ) -> None: """ Transfer ether between accounts. @@ -186,6 +187,11 @@ def destroy_account(self, address: Address) -> None: # noqa: U100 Used in SELFDESTRUCT and account cleanup. """ + def destroy_storage(self, address: Address) -> None: # noqa: U100 + """ + Completely remove the storage at address. + """ + def modify_state( self, address: Address, # noqa: U100 @@ -201,3 +207,7 @@ def modify_state( modifier_function : Callable[[Account], None] Function that takes an account and modifies it in place """ + + @property + def state(self) -> Any: # noqa: U100 + """Access to underlying state for compatibility.""" diff --git a/src/ethereum/state_oracle/memory_oracle.py b/src/ethereum/state_oracle/memory_oracle.py index 144fd398e9..61b498f843 100644 --- a/src/ethereum/state_oracle/memory_oracle.py +++ b/src/ethereum/state_oracle/memory_oracle.py @@ -6,9 +6,10 @@ This is mainly done as a first pass to make the diff small. """ -from typing import Any, Optional +from typing import Any, Callable, Optional from ethereum_types.bytes import Bytes20, Bytes32 +from ethereum_types.numeric import U256 # TODO: This file is current Osaka specific -- we could move state.py # into here to mitigate this. @@ -16,6 +17,7 @@ # Use generic types for compatibility across forks Account = Any Address = Bytes20 +State = Any class MemoryMerkleOracle: @@ -23,7 +25,7 @@ class MemoryMerkleOracle: Merkle oracle implementation that wraps existing execution-specs state. """ - def __init__(self, state): + def __init__(self, state: State) -> None: """ Initialize oracle with existing state. @@ -67,101 +69,111 @@ def state_root(self) -> Bytes32: return state_root(self._state) - def get_storage_original(self, address, key): + def get_storage_original(self, address: Address, key: Bytes32) -> Bytes32: """Get original storage value (before transaction started).""" from ethereum.osaka.state import get_storage_original storage_value = get_storage_original(self._state, address, key) return storage_value.to_be_bytes32() - def set_storage_value(self, address, key, value): + def set_storage_value( + self, address: Address, key: Bytes32, value: Bytes32 + ) -> None: """Set a single storage value.""" from ethereum_types.numeric import U256 from ethereum.osaka.state import set_storage - if isinstance(value, bytes): - storage_value = U256.from_be_bytes(value) - else: - storage_value = value + # Convert Bytes32 to U256 for storage + storage_value = U256.from_be_bytes(value) set_storage(self._state, address, key, storage_value) - def account_has_code_or_nonce(self, address): + def account_has_code_or_nonce(self, address: Address) -> bool: """Check if account has non-zero code or nonce.""" from ethereum.osaka.state import account_has_code_or_nonce return account_has_code_or_nonce(self._state, address) - def account_has_storage(self, address): + def account_has_storage(self, address: Address) -> bool: """Check if account has any storage slots.""" from ethereum.osaka.state import account_has_storage return account_has_storage(self._state, address) - def is_account_alive(self, address): + def is_account_alive(self, address: Address) -> bool: """Check if account is alive (exists and not marked for deletion).""" from ethereum.osaka.state import is_account_alive return is_account_alive(self._state, address) - def account_exists(self, address): + def account_exists(self, address: Address) -> bool: """Check if account exists in the state.""" from ethereum.osaka.state import account_exists return account_exists(self._state, address) - def increment_nonce(self, address): + def increment_nonce(self, address: Address) -> None: """Increment account nonce.""" from ethereum.osaka.state import increment_nonce increment_nonce(self._state, address) - def set_code(self, address, code): + def set_code(self, address: Address, code: Any) -> None: """Set account code.""" from ethereum.osaka.state import set_code set_code(self._state, address, code) - def set_account_balance(self, address, balance): + def set_account_balance(self, address: Address, balance: U256) -> None: """Set account balance.""" from ethereum.osaka.state import set_account_balance set_account_balance(self._state, address, balance) - def move_ether(self, sender, recipient, amount): + def move_ether( + self, sender: Address, recipient: Address, amount: U256 + ) -> None: """Transfer ether between accounts.""" from ethereum.osaka.state import move_ether move_ether(self._state, sender, recipient, amount) - def add_created_account(self, address): + def add_created_account(self, address: Address) -> None: """Mark account as created in current transaction.""" # Add to the created_accounts set in state self._state.created_accounts.add(address) - def is_created_account(self, address): + def is_created_account(self, address: Address) -> bool: """Check if account was created in current transaction.""" return address in self._state.created_accounts - def account_exists_and_is_empty(self, address): + def account_exists_and_is_empty(self, address: Address) -> bool: """Check if account exists and is empty.""" from ethereum.osaka.state import account_exists_and_is_empty return account_exists_and_is_empty(self._state, address) - def destroy_account(self, address): + def destroy_account(self, address: Address) -> None: """Mark account for destruction.""" from ethereum.osaka.state import destroy_account destroy_account(self._state, address) - def modify_state(self, address, modifier_function): + def destroy_storage(self, address: Address) -> None: + """Completely remove the storage at address.""" + from ethereum.osaka.state import destroy_storage + + destroy_storage(self._state, address) + + def modify_state( + self, address: Address, modifier_function: Callable[[Account], None] + ) -> None: """Modify an account using a modifier function.""" from ethereum.osaka.state import modify_state modify_state(self._state, address, modifier_function) @property - def state(self): + def state(self) -> State: """Access to underlying state for compatibility.""" return self._state From 72df53469af3f8a6b5c10123c7b34d9af9923161 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Sun, 17 Aug 2025 20:36:16 +0100 Subject: [PATCH 09/11] temp: hack --- src/ethereum_spec_tools/evm_tools/t8n/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index f00e049357..a4756f2e26 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -132,14 +132,24 @@ def block_environment(self) -> Any: Create the environment for the transaction. The keyword arguments are adjusted according to the fork. """ + # Handle oracle vs state for different forks + # TODO: Hack imo + if self.fork.is_after_fork("ethereum.osaka"): + from ethereum.state_oracle import MemoryMerkleOracle + + oracle = MemoryMerkleOracle(self.alloc.state) + state_or_oracle = {"oracle": oracle} + else: + state_or_oracle = {"state": self.alloc.state} + kw_arguments = { "block_hashes": self.env.block_hashes, "coinbase": self.env.coinbase, "number": self.env.block_number, "time": self.env.block_timestamp, - "state": self.alloc.state, "block_gas_limit": self.env.block_gas_limit, "chain_id": self.chain_id, + **state_or_oracle, } if self.fork.is_after_fork("ethereum.london"): From c07b1754e50256179caf184b43374acda4af682b Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Mon, 18 Aug 2025 21:35:49 +0100 Subject: [PATCH 10/11] evm_tracer like approach --- src/ethereum/osaka/fork.py | 60 ++++++++++--------- src/ethereum/osaka/utils/message.py | 4 +- src/ethereum/osaka/vm/__init__.py | 18 ++++-- src/ethereum/osaka/vm/eoa_delegation.py | 4 +- .../osaka/vm/instructions/environment.py | 10 ++-- src/ethereum/osaka/vm/instructions/storage.py | 4 +- src/ethereum/osaka/vm/instructions/system.py | 10 ++-- src/ethereum/osaka/vm/interpreter.py | 22 +++---- src/ethereum/state_oracle/__init__.py | 33 ++++++++++ .../evm_tools/t8n/__init__.py | 16 ++--- 10 files changed, 115 insertions(+), 66 deletions(-) diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index e8db50f836..972be555af 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -13,7 +13,7 @@ """ from dataclasses import dataclass -from typing import Any, List, Optional, Tuple +from typing import List, Optional, Tuple from ethereum_rlp import rlp from ethereum_types.bytes import Bytes @@ -179,9 +179,7 @@ def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: return recent_block_hashes -def state_transition( - chain: BlockChain, block: Block, oracle: Optional[Any] = None -) -> None: +def state_transition(chain: BlockChain, block: Block) -> None: """ Attempts to apply a block to an existing block chain. @@ -212,13 +210,9 @@ def state_transition( if block.ommers != (): raise InvalidBlock - # Oracle must be provided - if oracle is None: - raise ValueError("Oracle parameter is required for state_transition") - block_env = vm.BlockEnvironment( chain_id=chain.chain_id, - oracle=oracle, + state=chain.state, block_gas_limit=block.header.gas_limit, block_hashes=get_last_256_block_hashes(chain), coinbase=block.header.coinbase, @@ -235,7 +229,7 @@ def state_transition( transactions=block.transactions, withdrawals=block.withdrawals, ) - block_state_root = block_env.oracle.state_root() + block_state_root = block_env.get_oracle().state_root() transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -459,7 +453,7 @@ def check_transaction( raise BlobGasLimitExceededError("blob gas limit exceeded") sender_address = recover_sender(block_env.chain_id, tx) - sender_account = block_env.oracle.get_account(sender_address) + sender_account = block_env.get_oracle().get_account(sender_address) if isinstance( tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) @@ -664,7 +658,9 @@ def process_checked_system_transaction( system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = block_env.oracle.get_account(target_address).code + system_contract_code = ( + block_env.get_oracle().get_account(target_address).code + ) if len(system_contract_code) == 0: raise InvalidBlock( @@ -711,7 +707,9 @@ def process_unchecked_system_transaction( system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = block_env.oracle.get_account(target_address).code + system_contract_code = ( + block_env.get_oracle().get_account(target_address).code + ) return process_system_transaction( block_env, target_address, @@ -868,7 +866,7 @@ def process_transaction( tx=tx, ) - sender_account = block_env.oracle.get_account(sender) + sender_account = block_env.get_oracle().get_account(sender) if isinstance(tx, BlobTransaction): blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) @@ -878,12 +876,12 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - block_env.oracle.increment_nonce(sender) + block_env.get_oracle().increment_nonce(sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee ) - block_env.oracle.set_account_balance( + block_env.get_oracle().set_account_balance( sender, U256(sender_balance_after_gas_fee) ) @@ -947,29 +945,33 @@ def process_transaction( transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas # refund gas - current_sender_balance = block_env.oracle.get_account(sender).balance + current_sender_balance = block_env.get_oracle().get_account(sender).balance sender_balance_after_refund = current_sender_balance + U256( gas_refund_amount ) - block_env.oracle.set_account_balance(sender, sender_balance_after_refund) + block_env.get_oracle().set_account_balance( + sender, sender_balance_after_refund + ) # transfer miner fees - current_coinbase_balance = block_env.oracle.get_account( - block_env.coinbase - ).balance + current_coinbase_balance = ( + block_env.get_oracle().get_account(block_env.coinbase).balance + ) coinbase_balance_after_mining_fee = current_coinbase_balance + U256( transaction_fee ) if coinbase_balance_after_mining_fee != 0: - block_env.oracle.set_account_balance( + block_env.get_oracle().set_account_balance( block_env.coinbase, coinbase_balance_after_mining_fee, ) - elif block_env.oracle.account_exists_and_is_empty(block_env.coinbase): - block_env.oracle.destroy_account(block_env.coinbase) + elif block_env.get_oracle().account_exists_and_is_empty( + block_env.coinbase + ): + block_env.get_oracle().destroy_account(block_env.coinbase) for address in tx_output.accounts_to_delete: - block_env.oracle.destroy_account(address) + block_env.get_oracle().destroy_account(address) block_output.block_gas_used += tx_gas_used_after_refund block_output.blob_gas_used += tx_blob_gas_used @@ -1009,10 +1011,12 @@ def increase_recipient_balance(recipient: Account) -> None: rlp.encode(wd), ) - block_env.oracle.modify_state(wd.address, increase_recipient_balance) + block_env.get_oracle().modify_state( + wd.address, increase_recipient_balance + ) - if block_env.oracle.account_exists_and_is_empty(wd.address): - block_env.oracle.destroy_account(wd.address) + if block_env.get_oracle().account_exists_and_is_empty(wd.address): + block_env.get_oracle().destroy_account(wd.address) def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py index ed8dfec6a3..c7a2fc3322 100644 --- a/src/ethereum/osaka/utils/message.py +++ b/src/ethereum/osaka/utils/message.py @@ -53,7 +53,7 @@ def prepare_message( if isinstance(tx.to, Bytes0): current_target = compute_contract_address( tx_env.origin, - block_env.oracle.get_account(tx_env.origin).nonce - Uint(1), + block_env.get_oracle().get_account(tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") code = tx.data @@ -61,7 +61,7 @@ def prepare_message( elif isinstance(tx.to, Address): current_target = tx.to msg_data = tx.data - code = block_env.oracle.get_account(tx.to).code + code = block_env.get_oracle().get_account(tx.to).code code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py index a18365dc51..a48af28f61 100644 --- a/src/ethereum/osaka/vm/__init__.py +++ b/src/ethereum/osaka/vm/__init__.py @@ -14,18 +14,17 @@ """ from dataclasses import dataclass, field -from typing import List, Optional, Set, Tuple +from typing import Any, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ethereum.state_oracle import MerkleOracle from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, Authorization, VersionedHash -from ..state import TransientStorage +from ..state import State, TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie @@ -39,7 +38,8 @@ class BlockEnvironment: """ chain_id: U64 - oracle: MerkleOracle + # TODO: Remove, this is no longer being used. Kept so all tests don't break for now. + state: State block_gas_limit: Uint block_hashes: List[Hash32] coinbase: Address @@ -50,6 +50,16 @@ class BlockEnvironment: excess_blob_gas: U64 parent_beacon_block_root: Hash32 + def get_oracle(self) -> Any: + """ + Get the state oracle. + + Returns the global oracle (raises error if not set). + """ + from ethereum.state_oracle import get_state_oracle + + return get_state_oracle() + @dataclass class BlockOutput: diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py index 256b8184cd..701cdc5f75 100644 --- a/src/ethereum/osaka/vm/eoa_delegation.py +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -128,7 +128,7 @@ def access_delegation( delegation : `Tuple[bool, Address, Bytes, Uint]` The delegation address, code, and access gas cost. """ - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() code = oracle.get_account(address).code if not is_valid_delegation(code): return False, address, code, Uint(0) @@ -160,7 +160,7 @@ def set_delegation(message: Message) -> U256: refund_counter: `U256` Refund from authority which already exists in state. """ - oracle = message.block_env.oracle + oracle = message.block_env.get_oracle() refund_counter = U256(0) for auth in message.tx_env.authorizations: if auth.chain_id not in (message.block_env.chain_id, U256(0)): diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py index 9f15d07bab..c8ac3812ee 100644 --- a/src/ethereum/osaka/vm/instructions/environment.py +++ b/src/ethereum/osaka/vm/instructions/environment.py @@ -83,7 +83,7 @@ def balance(evm: Evm) -> None: charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. balance = oracle.get_account(address).balance @@ -351,7 +351,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() code = oracle.get_account(address).code codesize = U256(len(code)) @@ -394,7 +394,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() code = oracle.get_account(address).code value = buffer_read(code, code_start_index, size) @@ -481,7 +481,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() account = oracle.get_account(address) if account == EMPTY_ACCOUNT: @@ -513,7 +513,7 @@ def self_balance(evm: Evm) -> None: charge_gas(evm, GAS_FAST_STEP) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. balance = oracle.get_account(evm.message.current_target).balance diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py index a018b8d301..f583bb90cd 100644 --- a/src/ethereum/osaka/vm/instructions/storage.py +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -51,7 +51,7 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() value_bytes = oracle.get_storage(evm.message.current_target, key) value = U256.from_be_bytes(value_bytes) @@ -77,7 +77,7 @@ def sstore(evm: Evm) -> None: if evm.gas_left <= GAS_CALL_STIPEND: raise OutOfGasError - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() original_value_bytes = oracle.get_storage_original( evm.message.current_target, key ) diff --git a/src/ethereum/osaka/vm/instructions/system.py b/src/ethereum/osaka/vm/instructions/system.py index 1f0c5fa225..a575c13174 100644 --- a/src/ethereum/osaka/vm/instructions/system.py +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -82,7 +82,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() sender = oracle.get_account(sender_address) if ( @@ -159,7 +159,7 @@ def create(evm: Evm) -> None: charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, @@ -374,7 +374,7 @@ def call(evm: Evm) -> None: ) = access_delegation(evm, code_address) access_gas_cost += delegated_access_gas_cost - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() create_gas_cost = GAS_NEW_ACCOUNT if value == 0 or oracle.is_account_alive(to): create_gas_cost = Uint(0) @@ -471,7 +471,7 @@ def callcode(evm: Evm) -> None: charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() evm.memory += b"\x00" * extend_memory.expand_by sender_balance = oracle.get_account(evm.message.current_target).balance if sender_balance < value: @@ -518,7 +518,7 @@ def selfdestruct(evm: Evm) -> None: evm.accessed_addresses.add(beneficiary) gas_cost += GAS_COLD_ACCOUNT_ACCESS - oracle = evm.message.block_env.oracle + oracle = evm.message.block_env.get_oracle() current_balance = oracle.get_account(evm.message.current_target).balance if not oracle.is_account_alive(beneficiary) and current_balance != 0: diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py index eb79f51601..ca62ec5761 100644 --- a/src/ethereum/osaka/vm/interpreter.py +++ b/src/ethereum/osaka/vm/interpreter.py @@ -96,9 +96,9 @@ def process_message_call(message: Message) -> MessageCallOutput: block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = block_env.oracle.account_has_code_or_nonce( + is_collision = block_env.get_oracle().account_has_code_or_nonce( message.current_target - ) or block_env.oracle.account_has_storage(message.current_target) + ) or block_env.get_oracle().account_has_storage(message.current_target) if is_collision: return MessageCallOutput( Uint(0), @@ -118,7 +118,9 @@ def process_message_call(message: Message) -> MessageCallOutput: if delegated_address is not None: message.disable_precompiles = True message.accessed_addresses.add(delegated_address) - message.code = block_env.oracle.get_account(delegated_address).code + message.code = ( + block_env.get_oracle().get_account(delegated_address).code + ) message.code_address = delegated_address evm = process_message(message) @@ -160,7 +162,7 @@ def process_create_message(message: Message) -> Evm: evm: :py:class:`~ethereum.osaka.vm.Evm` Items containing execution specific objects. """ - state = message.block_env.oracle.state + state = message.block_env.get_oracle().state transient_storage = message.tx_env.transient_storage # take snapshot of state before processing the message begin_transaction(state, transient_storage) @@ -172,15 +174,15 @@ def process_create_message(message: Message) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - message.block_env.oracle.destroy_storage(message.current_target) + message.block_env.get_oracle().destroy_storage(message.current_target) # In the previously mentioned edge case the preexisting storage is ignored # for gas refund purposes. In order to do this we must track created # accounts. This tracking is also needed to respect the constraints # added to SELFDESTRUCT by EIP-6780. - message.block_env.oracle.add_created_account(message.current_target) + message.block_env.get_oracle().add_created_account(message.current_target) - message.block_env.oracle.increment_nonce(message.current_target) + message.block_env.get_oracle().increment_nonce(message.current_target) evm = process_message(message) if not evm.error: contract_code = evm.output @@ -198,7 +200,7 @@ def process_create_message(message: Message) -> Evm: evm.output = b"" evm.error = error else: - message.block_env.oracle.set_code( + message.block_env.get_oracle().set_code( message.current_target, contract_code ) commit_transaction(state, transient_storage) @@ -221,7 +223,7 @@ def process_message(message: Message) -> Evm: evm: :py:class:`~ethereum.osaka.vm.Evm` Items containing execution specific objects """ - state = message.block_env.oracle.state + state = message.block_env.get_oracle().state transient_storage = message.tx_env.transient_storage if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") @@ -230,7 +232,7 @@ def process_message(message: Message) -> Evm: begin_transaction(state, transient_storage) if message.should_transfer_value and message.value != 0: - message.block_env.oracle.move_ether( + message.block_env.get_oracle().move_ether( message.caller, message.current_target, message.value ) diff --git a/src/ethereum/state_oracle/__init__.py b/src/ethereum/state_oracle/__init__.py index 158a1cb7ac..933cd84505 100644 --- a/src/ethereum/state_oracle/__init__.py +++ b/src/ethereum/state_oracle/__init__.py @@ -5,10 +5,43 @@ by different state management strategies. """ +from typing import Optional + from .interface import MerkleOracle from .memory_oracle import MemoryMerkleOracle +_state_oracle: Optional[MerkleOracle] = None + + +def set_state_oracle(oracle: MerkleOracle) -> Optional[MerkleOracle]: + """ + Set the global state oracle. + + Returns the previous oracle if any. + """ + global _state_oracle + old = _state_oracle + _state_oracle = oracle + return old + + +def get_state_oracle() -> MerkleOracle: + """ + Get the current global state oracle. + + Raises RuntimeError if no oracle has been set. + """ + global _state_oracle + if _state_oracle is None: + raise RuntimeError( + "No global state oracle set. Call set_state_oracle() first." + ) + return _state_oracle + + __all__ = [ "MerkleOracle", "MemoryMerkleOracle", + "set_state_oracle", + "get_state_oracle", ] diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index a4756f2e26..edf8b54182 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -132,24 +132,24 @@ def block_environment(self) -> Any: Create the environment for the transaction. The keyword arguments are adjusted according to the fork. """ - # Handle oracle vs state for different forks - # TODO: Hack imo + # Set up global oracle for Osaka fork if self.fork.is_after_fork("ethereum.osaka"): - from ethereum.state_oracle import MemoryMerkleOracle + from ethereum.state_oracle import ( + MemoryMerkleOracle, + set_state_oracle, + ) - oracle = MemoryMerkleOracle(self.alloc.state) - state_or_oracle = {"oracle": oracle} - else: - state_or_oracle = {"state": self.alloc.state} + set_state_oracle(MemoryMerkleOracle(self.alloc.state)) kw_arguments = { "block_hashes": self.env.block_hashes, "coinbase": self.env.coinbase, "number": self.env.block_number, "time": self.env.block_timestamp, + # TODO: Remove this, since its not being used. + "state": self.alloc.state, "block_gas_limit": self.env.block_gas_limit, "chain_id": self.chain_id, - **state_or_oracle, } if self.fork.is_after_fork("ethereum.london"): From 4a46cb577c870ef6fc4f49e207a552985bcc4491 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Mon, 18 Aug 2025 21:41:04 +0100 Subject: [PATCH 11/11] tox --- src/ethereum/osaka/vm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py index a48af28f61..7fd61c1606 100644 --- a/src/ethereum/osaka/vm/__init__.py +++ b/src/ethereum/osaka/vm/__init__.py @@ -38,7 +38,7 @@ class BlockEnvironment: """ chain_id: U64 - # TODO: Remove, this is no longer being used. Kept so all tests don't break for now. + # TODO: Remove, no longer used. Kept so tests don't break for now. state: State block_gas_limit: Uint block_hashes: List[Hash32]