From 3c0f4b0a6bc40076cb46b9029ee2587262dff650 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Tue, 21 Jan 2025 16:33:15 +0100 Subject: [PATCH 01/70] prague initial commit --- src/ethereum/prague/__init__.py | 10 + src/ethereum/prague/blocks.py | 100 ++ src/ethereum/prague/bloom.py | 85 ++ src/ethereum/prague/exceptions.py | 24 + src/ethereum/prague/fork.py | 879 ++++++++++++++++++ src/ethereum/prague/fork_types.py | 64 ++ src/ethereum/prague/state.py | 734 +++++++++++++++ src/ethereum/prague/transactions.py | 453 +++++++++ src/ethereum/prague/trie.py | 470 ++++++++++ src/ethereum/prague/utils/__init__.py | 3 + src/ethereum/prague/utils/address.py | 93 ++ src/ethereum/prague/utils/hexadecimal.py | 68 ++ src/ethereum/prague/utils/message.py | 116 +++ src/ethereum/prague/vm/__init__.py | 151 +++ src/ethereum/prague/vm/exceptions.py | 140 +++ src/ethereum/prague/vm/gas.py | 362 ++++++++ .../prague/vm/instructions/__init__.py | 366 ++++++++ .../prague/vm/instructions/arithmetic.py | 373 ++++++++ .../prague/vm/instructions/bitwise.py | 240 +++++ src/ethereum/prague/vm/instructions/block.py | 249 +++++ .../prague/vm/instructions/comparison.py | 178 ++++ .../prague/vm/instructions/control_flow.py | 171 ++++ .../prague/vm/instructions/environment.py | 587 ++++++++++++ src/ethereum/prague/vm/instructions/keccak.py | 64 ++ src/ethereum/prague/vm/instructions/log.py | 88 ++ src/ethereum/prague/vm/instructions/memory.py | 177 ++++ src/ethereum/prague/vm/instructions/stack.py | 209 +++++ .../prague/vm/instructions/storage.py | 178 ++++ src/ethereum/prague/vm/instructions/system.py | 691 ++++++++++++++ src/ethereum/prague/vm/interpreter.py | 314 +++++++ src/ethereum/prague/vm/memory.py | 81 ++ .../vm/precompiled_contracts/__init__.py | 40 + .../vm/precompiled_contracts/alt_bn128.py | 154 +++ .../vm/precompiled_contracts/blake2f.py | 41 + .../vm/precompiled_contracts/ecrecover.py | 62 ++ .../vm/precompiled_contracts/identity.py | 38 + .../vm/precompiled_contracts/mapping.py | 49 + .../prague/vm/precompiled_contracts/modexp.py | 169 ++++ .../precompiled_contracts/point_evaluation.py | 72 ++ .../vm/precompiled_contracts/ripemd160.py | 43 + .../prague/vm/precompiled_contracts/sha256.py | 40 + src/ethereum/prague/vm/runtime.py | 67 ++ src/ethereum/prague/vm/stack.py | 59 ++ tests/prague/__init__.py | 0 tests/prague/test_evm_tools.py | 42 + tests/prague/test_rlp.py | 165 ++++ tests/prague/test_state_transition.py | 105 +++ tests/prague/test_trie.py | 89 ++ 48 files changed, 8953 insertions(+) create mode 100644 src/ethereum/prague/__init__.py create mode 100644 src/ethereum/prague/blocks.py create mode 100644 src/ethereum/prague/bloom.py create mode 100644 src/ethereum/prague/exceptions.py create mode 100644 src/ethereum/prague/fork.py create mode 100644 src/ethereum/prague/fork_types.py create mode 100644 src/ethereum/prague/state.py create mode 100644 src/ethereum/prague/transactions.py create mode 100644 src/ethereum/prague/trie.py create mode 100644 src/ethereum/prague/utils/__init__.py create mode 100644 src/ethereum/prague/utils/address.py create mode 100644 src/ethereum/prague/utils/hexadecimal.py create mode 100644 src/ethereum/prague/utils/message.py create mode 100644 src/ethereum/prague/vm/__init__.py create mode 100644 src/ethereum/prague/vm/exceptions.py create mode 100644 src/ethereum/prague/vm/gas.py create mode 100644 src/ethereum/prague/vm/instructions/__init__.py create mode 100644 src/ethereum/prague/vm/instructions/arithmetic.py create mode 100644 src/ethereum/prague/vm/instructions/bitwise.py create mode 100644 src/ethereum/prague/vm/instructions/block.py create mode 100644 src/ethereum/prague/vm/instructions/comparison.py create mode 100644 src/ethereum/prague/vm/instructions/control_flow.py create mode 100644 src/ethereum/prague/vm/instructions/environment.py create mode 100644 src/ethereum/prague/vm/instructions/keccak.py create mode 100644 src/ethereum/prague/vm/instructions/log.py create mode 100644 src/ethereum/prague/vm/instructions/memory.py create mode 100644 src/ethereum/prague/vm/instructions/stack.py create mode 100644 src/ethereum/prague/vm/instructions/storage.py create mode 100644 src/ethereum/prague/vm/instructions/system.py create mode 100644 src/ethereum/prague/vm/interpreter.py create mode 100644 src/ethereum/prague/vm/memory.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/__init__.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/blake2f.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/ecrecover.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/identity.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/mapping.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/modexp.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/point_evaluation.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/ripemd160.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/sha256.py create mode 100644 src/ethereum/prague/vm/runtime.py create mode 100644 src/ethereum/prague/vm/stack.py create mode 100644 tests/prague/__init__.py create mode 100644 tests/prague/test_evm_tools.py create mode 100644 tests/prague/test_rlp.py create mode 100644 tests/prague/test_state_transition.py create mode 100644 tests/prague/test_trie.py diff --git a/src/ethereum/prague/__init__.py b/src/ethereum/prague/__init__.py new file mode 100644 index 0000000000..04774a7578 --- /dev/null +++ b/src/ethereum/prague/__init__.py @@ -0,0 +1,10 @@ +""" +The Prague fork introduces transient storage, exposes beacon chain roots, +introduces a new blob-carrying transaction type, adds a memory copying +instruction, limits self-destruct to only work for contracts created in the +same transaction, and adds an instruction to read the blob base fee. +""" + +from ethereum.fork_criteria import ByTimestamp + +FORK_CRITERIA = ByTimestamp(1710338135) diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py new file mode 100644 index 0000000000..54ef88e15f --- /dev/null +++ b/src/ethereum/prague/blocks.py @@ -0,0 +1,100 @@ +""" +A `Block` is a single link in the chain that is Ethereum. Each `Block` contains +a `Header` and zero or more transactions. Each `Header` contains associated +metadata like the block number, parent block hash, and how much gas was +consumed by its transactions. + +Together, these blocks form a cryptographically secure journal recording the +history of all state transitions that have happened since the genesis of the +chain. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_types.bytes import Bytes, Bytes8, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint + +from ..crypto.hash import Hash32 +from .fork_types import Address, Bloom, Root +from .transactions import LegacyTransaction + + +@slotted_freezable +@dataclass +class Withdrawal: + """ + Withdrawals that have been validated on the consensus layer. + """ + + index: U64 + validator_index: U64 + address: Address + amount: U256 + + +@slotted_freezable +@dataclass +class Header: + """ + Header portion of a block on the chain. + """ + + parent_hash: Hash32 + ommers_hash: Hash32 + coinbase: Address + state_root: Root + transactions_root: Root + receipt_root: Root + bloom: Bloom + difficulty: Uint + number: Uint + gas_limit: Uint + gas_used: Uint + timestamp: U256 + extra_data: Bytes + prev_randao: Bytes32 + nonce: Bytes8 + base_fee_per_gas: Uint + withdrawals_root: Root + blob_gas_used: U64 + excess_blob_gas: U64 + parent_beacon_block_root: Root + + +@slotted_freezable +@dataclass +class Block: + """ + A complete block. + """ + + header: Header + transactions: Tuple[Union[Bytes, LegacyTransaction], ...] + ommers: Tuple[Header, ...] + withdrawals: Tuple[Withdrawal, ...] + + +@slotted_freezable +@dataclass +class Log: + """ + Data record produced during the execution of a transaction. + """ + + address: Address + topics: Tuple[Hash32, ...] + data: bytes + + +@slotted_freezable +@dataclass +class Receipt: + """ + Result of a transaction. + """ + + succeeded: bool + cumulative_gas_used: Uint + bloom: Bloom + logs: Tuple[Log, ...] diff --git a/src/ethereum/prague/bloom.py b/src/ethereum/prague/bloom.py new file mode 100644 index 0000000000..0ba6e431ab --- /dev/null +++ b/src/ethereum/prague/bloom.py @@ -0,0 +1,85 @@ +""" +Ethereum Logs Bloom +^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +This modules defines functions for calculating bloom filters of logs. For the +general theory of bloom filters see e.g. `Wikipedia +`_. Bloom filters are used to allow +for efficient searching of logs by address and/or topic, by rapidly +eliminating blocks and receipts from their search. +""" + +from typing import Tuple + +from ethereum_types.numeric import Uint + +from ethereum.crypto.hash import keccak256 + +from .blocks import Log +from .fork_types import Bloom + + +def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: + """ + Add a bloom entry to the bloom filter (`bloom`). + + The number of hash functions used is 3. They are calculated by taking the + least significant 11 bits from the first 3 16-bit words of the + `keccak_256()` hash of `bloom_entry`. + + Parameters + ---------- + bloom : + The bloom filter. + bloom_entry : + An entry which is to be added to bloom filter. + """ + hash = keccak256(bloom_entry) + + for idx in (0, 2, 4): + # Obtain the least significant 11 bits from the pair of bytes + # (16 bits), and set this bit in bloom bytearray. + # The obtained bit is 0-indexed in the bloom filter from the least + # significant bit to the most significant bit. + bit_to_set = Uint.from_be_bytes(hash[idx : idx + 2]) & Uint(0x07FF) + # Below is the index of the bit in the bytearray (where 0-indexed + # byte is the most significant byte) + bit_index = 0x07FF - int(bit_to_set) + + byte_index = bit_index // 8 + bit_value = 1 << (7 - (bit_index % 8)) + bloom[byte_index] = bloom[byte_index] | bit_value + + +def logs_bloom(logs: Tuple[Log, ...]) -> Bloom: + """ + Obtain the logs bloom from a list of log entries. + + The address and each topic of a log are added to the bloom filter. + + Parameters + ---------- + logs : + List of logs for which the logs bloom is to be obtained. + + Returns + ------- + logs_bloom : `Bloom` + The logs bloom obtained which is 256 bytes with some bits set as per + the caller address and the log topics. + """ + bloom: bytearray = bytearray(b"\x00" * 256) + + for log in logs: + add_to_bloom(bloom, log.address) + for topic in log.topics: + add_to_bloom(bloom, topic) + + return Bloom(bloom) diff --git a/src/ethereum/prague/exceptions.py b/src/ethereum/prague/exceptions.py new file mode 100644 index 0000000000..5781a2c1c3 --- /dev/null +++ b/src/ethereum/prague/exceptions.py @@ -0,0 +1,24 @@ +""" +Exceptions specific to this fork. +""" + +from typing import Final + +from ethereum.exceptions import InvalidTransaction + + +class TransactionTypeError(InvalidTransaction): + """ + Unknown [EIP-2718] transaction type byte. + + [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + """ + + transaction_type: Final[int] + """ + The type byte of the transaction that caused the error. + """ + + def __init__(self, transaction_type: int): + super().__init__(f"unknown transaction type `{transaction_type}`") + self.transaction_type = transaction_type diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py new file mode 100644 index 0000000000..5bbb517ed6 --- /dev/null +++ b/src/ethereum/prague/fork.py @@ -0,0 +1,879 @@ +""" +Ethereum Specification +^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Entry point for the Ethereum specification. +""" + +from dataclasses import dataclass +from typing import List, Optional, Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidBlock, InvalidSenderError + +from . import vm +from .blocks import Block, Header, Log, Receipt, Withdrawal +from .bloom import logs_bloom +from .fork_types import Address, Bloom, Root, VersionedHash +from .state import ( + State, + TransientStorage, + account_exists_and_is_empty, + destroy_account, + destroy_touched_empty_accounts, + get_account, + increment_nonce, + process_withdrawal, + set_account_balance, + state_root, +) +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, + calculate_intrinsic_cost, + decode_transaction, + encode_transaction, + recover_sender, + validate_transaction, +) +from .trie import Trie, root, trie_set +from .utils.hexadecimal import hex_to_address +from .utils.message import prepare_message +from .vm import Message +from .vm.gas import ( + calculate_blob_gas_price, + calculate_data_fee, + calculate_excess_blob_gas, + calculate_total_blob_gas, +) +from .vm.interpreter import process_message_call + +BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) +ELASTICITY_MULTIPLIER = Uint(2) +GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) +GAS_LIMIT_MINIMUM = Uint(5000) +EMPTY_OMMER_HASH = keccak256(rlp.encode([])) +SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") +BEACON_ROOTS_ADDRESS = hex_to_address( + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" +) +SYSTEM_TRANSACTION_GAS = Uint(30000000) +MAX_BLOB_GAS_PER_BLOCK = Uint(786432) +VERSIONED_HASH_VERSION_KZG = b"\x01" + + +@dataclass +class BlockChain: + """ + History and current state of the block chain. + """ + + blocks: List[Block] + state: State + chain_id: U64 + + +def apply_fork(old: BlockChain) -> BlockChain: + """ + Transforms the state from the previous hard fork (`old`) into the block + chain object for this hard fork and returns it. + + When forks need to implement an irregular state transition, this function + is used to handle the irregularity. See the :ref:`DAO Fork ` for + an example. + + Parameters + ---------- + old : + Previous block chain object. + + Returns + ------- + new : `BlockChain` + Upgraded block chain object for this hard fork. + """ + return old + + +def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: + """ + Obtain the list of hashes of the previous 256 blocks in order of + increasing block number. + + This function will return less hashes for the first 256 blocks. + + The ``BLOCKHASH`` opcode needs to access the latest hashes on the chain, + therefore this function retrieves them. + + Parameters + ---------- + chain : + History and current state. + + Returns + ------- + recent_block_hashes : `List[Hash32]` + Hashes of the recent 256 blocks in order of increasing block number. + """ + recent_blocks = chain.blocks[-255:] + # TODO: This function has not been tested rigorously + if len(recent_blocks) == 0: + return [] + + recent_block_hashes = [] + + for block in recent_blocks: + prev_block_hash = block.header.parent_hash + recent_block_hashes.append(prev_block_hash) + + # We are computing the hash only for the most recent block and not for + # the rest of the blocks as they have successors which have the hash of + # the current block as parent hash. + most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header)) + recent_block_hashes.append(most_recent_block_hash) + + return recent_block_hashes + + +def state_transition(chain: BlockChain, block: Block) -> None: + """ + Attempts to apply a block to an existing block chain. + + All parts of the block's contents need to be verified before being added + to the chain. Blocks are verified by ensuring that the contents of the + block make logical sense with the contents of the parent block. The + information in the block's header must also match the corresponding + information in the block. + + To implement Ethereum, in theory clients are only required to store the + most recent 255 blocks of the chain since as far as execution is + concerned, only those blocks are accessed. Practically, however, clients + should store more blocks to handle reorgs. + + Parameters + ---------- + chain : + History and current state. + block : + Block to apply to `chain`. + """ + parent_header = chain.blocks[-1].header + excess_blob_gas = calculate_excess_blob_gas(parent_header) + if block.header.excess_blob_gas != excess_blob_gas: + raise InvalidBlock + + validate_header(block.header, parent_header) + if block.ommers != (): + raise InvalidBlock + apply_body_output = apply_body( + chain.state, + get_last_256_block_hashes(chain), + block.header.coinbase, + block.header.number, + block.header.base_fee_per_gas, + block.header.gas_limit, + block.header.timestamp, + block.header.prev_randao, + block.transactions, + chain.chain_id, + block.withdrawals, + block.header.parent_beacon_block_root, + excess_blob_gas, + ) + if apply_body_output.block_gas_used != block.header.gas_used: + raise InvalidBlock( + f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + ) + if apply_body_output.transactions_root != block.header.transactions_root: + raise InvalidBlock + if apply_body_output.state_root != block.header.state_root: + raise InvalidBlock + if apply_body_output.receipt_root != block.header.receipt_root: + raise InvalidBlock + if apply_body_output.block_logs_bloom != block.header.bloom: + raise InvalidBlock + if apply_body_output.withdrawals_root != block.header.withdrawals_root: + raise InvalidBlock + if apply_body_output.blob_gas_used != block.header.blob_gas_used: + raise InvalidBlock + + chain.blocks.append(block) + if len(chain.blocks) > 255: + # Real clients have to store more blocks to deal with reorgs, but the + # protocol only requires the last 255 + chain.blocks = chain.blocks[-255:] + + +def calculate_base_fee_per_gas( + block_gas_limit: Uint, + parent_gas_limit: Uint, + parent_gas_used: Uint, + parent_base_fee_per_gas: Uint, +) -> Uint: + """ + Calculates the base fee per gas for the block. + + Parameters + ---------- + block_gas_limit : + Gas limit of the block for which the base fee is being calculated. + parent_gas_limit : + Gas limit of the parent block. + parent_gas_used : + Gas used in the parent block. + parent_base_fee_per_gas : + Base fee per gas of the parent block. + + Returns + ------- + base_fee_per_gas : `Uint` + Base fee per gas for the block. + """ + parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER + if not check_gas_limit(block_gas_limit, parent_gas_limit): + raise InvalidBlock + + if parent_gas_used == parent_gas_target: + expected_base_fee_per_gas = parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = max( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR, + Uint(1), + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas + base_fee_per_gas_delta + ) + else: + gas_used_delta = parent_gas_target - parent_gas_used + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = ( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas - base_fee_per_gas_delta + ) + + return Uint(expected_base_fee_per_gas) + + +def validate_header(header: Header, parent_header: Header) -> None: + """ + Verifies a block header. + + In order to consider a block's header valid, the logic for the + quantities in the header should match the logic for the block itself. + For example the header timestamp should be greater than the block's parent + timestamp because the block was created *after* the parent block. + Additionally, the block's number should be directly following the parent + block's number since it is the next block in the sequence. + + Parameters + ---------- + header : + Header to check for correctness. + parent_header : + Parent Header of the header to check for correctness + """ + if header.gas_used > header.gas_limit: + raise InvalidBlock + + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_header.base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: + raise InvalidBlock + if header.number != parent_header.number + Uint(1): + raise InvalidBlock + if len(header.extra_data) > 32: + raise InvalidBlock + if header.difficulty != 0: + raise InvalidBlock + if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00": + raise InvalidBlock + if header.ommers_hash != EMPTY_OMMER_HASH: + raise InvalidBlock + + block_parent_hash = keccak256(rlp.encode(parent_header)) + if header.parent_hash != block_parent_hash: + raise InvalidBlock + + +def check_transaction( + state: State, + tx: Transaction, + gas_available: Uint, + chain_id: U64, + base_fee_per_gas: Uint, + excess_blob_gas: U64, +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...]]: + """ + Check if the transaction is includable in the block. + + Parameters + ---------- + state : + Current state. + tx : + The transaction. + gas_available : + The gas remaining in the block. + chain_id : + The ID of the current chain. + base_fee_per_gas : + The block base fee. + excess_blob_gas : + The excess blob gas. + + Returns + ------- + sender_address : + The sender of the transaction. + effective_gas_price : + The price to charge for gas when the transaction is executed. + blob_versioned_hashes : + The blob versioned hashes of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not includable. + """ + if tx.gas > gas_available: + raise InvalidBlock + sender_address = recover_sender(chain_id, tx) + sender_account = get_account(state, sender_address) + + if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise InvalidBlock + if tx.max_fee_per_gas < base_fee_per_gas: + raise InvalidBlock + + priority_fee_per_gas = min( + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas - base_fee_per_gas, + ) + effective_gas_price = priority_fee_per_gas + base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas + else: + if tx.gas_price < base_fee_per_gas: + raise InvalidBlock + effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if isinstance(tx, BlobTransaction): + if not isinstance(tx.to, Address): + raise InvalidBlock + if len(tx.blob_versioned_hashes) == 0: + raise InvalidBlock + for blob_versioned_hash in tx.blob_versioned_hashes: + if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: + raise InvalidBlock + + blob_gas_price = calculate_blob_gas_price(excess_blob_gas) + if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: + raise InvalidBlock + + max_gas_fee += calculate_total_blob_gas(tx) * Uint( + tx.max_fee_per_blob_gas + ) + blob_versioned_hashes = tx.blob_versioned_hashes + else: + blob_versioned_hashes = () + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code != bytearray(): + raise InvalidSenderError("not EOA") + + return sender_address, effective_gas_price, blob_versioned_hashes + + +def make_receipt( + tx: Transaction, + error: Optional[Exception], + cumulative_gas_used: Uint, + logs: Tuple[Log, ...], +) -> Union[Bytes, Receipt]: + """ + Make the receipt for a transaction that was executed. + + Parameters + ---------- + tx : + The executed transaction. + error : + Error in the top level frame of the transaction, if any. + cumulative_gas_used : + The total gas used so far in the block after the transaction was + executed. + logs : + The logs produced by the transaction. + + Returns + ------- + receipt : + The receipt for the transaction. + """ + receipt = Receipt( + succeeded=error is None, + cumulative_gas_used=cumulative_gas_used, + bloom=logs_bloom(logs), + logs=logs, + ) + + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(receipt) + else: + return receipt + + +@dataclass +class ApplyBodyOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_root : `ethereum.fork_types.Root` + Trie root of all the transactions in the block. + receipt_root : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs_bloom : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + state_root : `ethereum.fork_types.Root` + State root after all transactions have been executed. + withdrawals_root : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + blob_gas_used : `ethereum.base_types.Uint` + Total blob gas used in the block. + """ + + block_gas_used: Uint + transactions_root: Root + receipt_root: Root + block_logs_bloom: Bloom + state_root: Root + withdrawals_root: Root + blob_gas_used: Uint + + +def apply_body( + state: State, + block_hashes: List[Hash32], + coinbase: Address, + block_number: Uint, + base_fee_per_gas: Uint, + block_gas_limit: Uint, + block_time: U256, + prev_randao: Bytes32, + transactions: Tuple[Union[LegacyTransaction, Bytes], ...], + chain_id: U64, + withdrawals: Tuple[Withdrawal, ...], + parent_beacon_block_root: Root, + excess_blob_gas: U64, +) -> ApplyBodyOutput: + """ + Executes a block. + + Many of the contents of a block are stored in data structures called + tries. There is a transactions trie which is similar to a ledger of the + transactions stored in the current block. There is also a receipts trie + which stores the results of executing a transaction, like the post state + and gas used. This function creates and executes the block that is to be + added to the chain. + + Parameters + ---------- + state : + Current account state. + block_hashes : + List of hashes of the previous 256 blocks in the order of + increasing block number. + coinbase : + Address of account which receives block reward and transaction fees. + block_number : + Position of the block within the chain. + base_fee_per_gas : + Base fee per gas of within the block. + block_gas_limit : + Initial amount of gas available for execution in this block. + block_time : + Time the block was produced, measured in seconds since the epoch. + prev_randao : + The previous randao from the beacon chain. + transactions : + Transactions included in the block. + ommers : + Headers of ancestor blocks which are not direct parents (formerly + uncles.) + chain_id : + ID of the executing chain. + withdrawals : + Withdrawals to be processed in the current block. + parent_beacon_block_root : + The root of the beacon block from the parent block. + excess_blob_gas : + Excess blob gas calculated from the previous block. + + Returns + ------- + apply_body_output : `ApplyBodyOutput` + Output of applying the block body to the state. + """ + blob_gas_used = Uint(0) + gas_available = block_gas_limit + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = Trie(secured=False, default=None) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( + secured=False, default=None + ) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie( + secured=False, default=None + ) + block_logs: Tuple[Log, ...] = () + + beacon_block_roots_contract_code = get_account( + state, BEACON_ROOTS_ADDRESS + ).code + + system_tx_message = Message( + caller=SYSTEM_ADDRESS, + target=BEACON_ROOTS_ADDRESS, + gas=SYSTEM_TRANSACTION_GAS, + value=U256(0), + data=parent_beacon_block_root, + code=beacon_block_roots_contract_code, + depth=Uint(0), + current_target=BEACON_ROOTS_ADDRESS, + code_address=BEACON_ROOTS_ADDRESS, + should_transfer_value=False, + is_static=False, + accessed_addresses=set(), + accessed_storage_keys=set(), + parent_evm=None, + ) + + system_tx_env = vm.Environment( + caller=SYSTEM_ADDRESS, + origin=SYSTEM_ADDRESS, + block_hashes=block_hashes, + coinbase=coinbase, + number=block_number, + gas_limit=block_gas_limit, + base_fee_per_gas=base_fee_per_gas, + gas_price=base_fee_per_gas, + time=block_time, + prev_randao=prev_randao, + state=state, + chain_id=chain_id, + traces=[], + excess_blob_gas=excess_blob_gas, + blob_versioned_hashes=(), + transient_storage=TransientStorage(), + ) + + system_tx_output = process_message_call(system_tx_message, system_tx_env) + + destroy_touched_empty_accounts( + system_tx_env.state, system_tx_output.touched_accounts + ) + + for i, tx in enumerate(map(decode_transaction, transactions)): + trie_set( + transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) + ) + + ( + sender_address, + effective_gas_price, + blob_versioned_hashes, + ) = check_transaction( + state, + tx, + gas_available, + chain_id, + base_fee_per_gas, + excess_blob_gas, + ) + + env = vm.Environment( + caller=sender_address, + origin=sender_address, + block_hashes=block_hashes, + coinbase=coinbase, + number=block_number, + gas_limit=block_gas_limit, + base_fee_per_gas=base_fee_per_gas, + gas_price=effective_gas_price, + time=block_time, + prev_randao=prev_randao, + state=state, + chain_id=chain_id, + traces=[], + excess_blob_gas=excess_blob_gas, + blob_versioned_hashes=blob_versioned_hashes, + transient_storage=TransientStorage(), + ) + + gas_used, logs, error = process_transaction(env, tx) + gas_available -= gas_used + + receipt = make_receipt( + tx, error, (block_gas_limit - gas_available), logs + ) + + trie_set( + receipts_trie, + rlp.encode(Uint(i)), + receipt, + ) + + block_logs += logs + blob_gas_used += calculate_total_blob_gas(tx) + if blob_gas_used > MAX_BLOB_GAS_PER_BLOCK: + raise InvalidBlock + block_gas_used = block_gas_limit - gas_available + + block_logs_bloom = logs_bloom(block_logs) + + for i, wd in enumerate(withdrawals): + trie_set(withdrawals_trie, rlp.encode(Uint(i)), rlp.encode(wd)) + + process_withdrawal(state, wd) + + if account_exists_and_is_empty(state, wd.address): + destroy_account(state, wd.address) + + return ApplyBodyOutput( + block_gas_used, + root(transactions_trie), + root(receipts_trie), + block_logs_bloom, + state_root(state), + root(withdrawals_trie), + blob_gas_used, + ) + + +def process_transaction( + env: vm.Environment, tx: Transaction +) -> Tuple[Uint, Tuple[Log, ...], Optional[Exception]]: + """ + Execute a transaction against the provided environment. + + This function processes the actions needed to execute a transaction. + It decrements the sender's account after calculating the gas fee and + refunds them the proper amount after execution. Calling contracts, + deploying code, and incrementing nonces are all examples of actions that + happen within this function or from a call made within this function. + + Accounts that are marked for deletion are processed and destroyed after + execution. + + Parameters + ---------- + env : + Environment for the Ethereum Virtual Machine. + tx : + Transaction to execute. + + Returns + ------- + gas_left : `ethereum.base_types.U256` + Remaining gas after execution. + logs : `Tuple[ethereum.blocks.Log, ...]` + Logs generated during execution. + """ + if not validate_transaction(tx): + raise InvalidBlock + + sender = env.origin + sender_account = get_account(env.state, sender) + + if isinstance(tx, BlobTransaction): + blob_gas_fee = calculate_data_fee(env.excess_blob_gas, tx) + else: + blob_gas_fee = Uint(0) + + effective_gas_fee = tx.gas * env.gas_price + + gas = tx.gas - calculate_intrinsic_cost(tx) + increment_nonce(env.state, sender) + + sender_balance_after_gas_fee = ( + Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee + ) + set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + + preaccessed_addresses = set() + preaccessed_storage_keys = set() + preaccessed_addresses.add(env.coinbase) + if isinstance( + tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) + ): + for address, keys in tx.access_list: + preaccessed_addresses.add(address) + for key in keys: + preaccessed_storage_keys.add((address, key)) + + message = prepare_message( + sender, + tx.to, + tx.value, + tx.data, + gas, + env, + preaccessed_addresses=frozenset(preaccessed_addresses), + preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + ) + + output = process_message_call(message, env) + + gas_used = tx.gas - output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) + gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + + # For non-1559 transactions env.gas_price == tx.gas_price + priority_fee_per_gas = env.gas_price - env.base_fee_per_gas + transaction_fee = ( + tx.gas - output.gas_left - gas_refund + ) * priority_fee_per_gas + + total_gas_used = gas_used - gas_refund + + # refund gas + sender_balance_after_refund = get_account( + env.state, sender + ).balance + U256(gas_refund_amount) + set_account_balance(env.state, sender, sender_balance_after_refund) + + # transfer miner fees + coinbase_balance_after_mining_fee = get_account( + env.state, env.coinbase + ).balance + U256(transaction_fee) + if coinbase_balance_after_mining_fee != 0: + set_account_balance( + env.state, env.coinbase, coinbase_balance_after_mining_fee + ) + elif account_exists_and_is_empty(env.state, env.coinbase): + destroy_account(env.state, env.coinbase) + + for address in output.accounts_to_delete: + destroy_account(env.state, address) + + destroy_touched_empty_accounts(env.state, output.touched_accounts) + + return total_gas_used, output.logs, output.error + + +def compute_header_hash(header: Header) -> Hash32: + """ + Computes the hash of a block header. + + The header hash of a block is the canonical hash that is used to refer + to a specific block and completely distinguishes a block from another. + + ``keccak256`` is a function that produces a 256 bit hash of any input. + It also takes in any number of bytes as an input and produces a single + hash for them. A hash is a completely unique output for a single input. + So an input corresponds to one unique hash that can be used to identify + the input exactly. + + Prior to using the ``keccak256`` hash function, the header must be + encoded using the Recursive-Length Prefix. See :ref:`rlp`. + RLP encoding the header converts it into a space-efficient format that + allows for easy transfer of data between nodes. The purpose of RLP is to + encode arbitrarily nested arrays of binary data, and RLP is the primary + encoding method used to serialize objects in Ethereum's execution layer. + The only purpose of RLP is to encode structure; encoding specific data + types (e.g. strings, floats) is left up to higher-order protocols. + + Parameters + ---------- + header : + Header of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the header. + """ + return keccak256(rlp.encode(header)) + + +def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: + """ + Validates the gas limit for a block. + + The bounds of the gas limit, ``max_adjustment_delta``, is set as the + quotient of the parent block's gas limit and the + ``GAS_LIMIT_ADJUSTMENT_FACTOR``. Therefore, if the gas limit that is + passed through as a parameter is greater than or equal to the *sum* of + the parent's gas and the adjustment delta then the limit for gas is too + high and fails this function's check. Similarly, if the limit is less + than or equal to the *difference* of the parent's gas and the adjustment + delta *or* the predefined ``GAS_LIMIT_MINIMUM`` then this function's + check fails because the gas limit doesn't allow for a sufficient or + reasonable amount of gas to be used on a block. + + Parameters + ---------- + gas_limit : + Gas limit to validate. + + parent_gas_limit : + Gas limit of the parent block. + + Returns + ------- + check : `bool` + True if gas limit constraints are satisfied, False otherwise. + """ + max_adjustment_delta = parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR + if gas_limit >= parent_gas_limit + max_adjustment_delta: + return False + if gas_limit <= parent_gas_limit - max_adjustment_delta: + return False + if gas_limit < GAS_LIMIT_MINIMUM: + return False + + return True diff --git a/src/ethereum/prague/fork_types.py b/src/ethereum/prague/fork_types.py new file mode 100644 index 0000000000..dc287c84f1 --- /dev/null +++ b/src/ethereum/prague/fork_types.py @@ -0,0 +1,64 @@ +""" +Ethereum Types +^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Types re-used throughout the specification, which are specific to Ethereum. +""" + +from dataclasses import dataclass + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes20, Bytes256 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U256, Uint + +from ..crypto.hash import Hash32, keccak256 + +Address = Bytes20 +Root = Hash32 +VersionedHash = Hash32 + +Bloom = Bytes256 + + +@slotted_freezable +@dataclass +class Account: + """ + State associated with an address. + """ + + nonce: Uint + balance: U256 + code: bytes + + +EMPTY_ACCOUNT = Account( + nonce=Uint(0), + balance=U256(0), + code=bytearray(), +) + + +def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: + """ + Encode `Account` dataclass. + + Storage is not stored in the `Account` dataclass, so `Accounts` cannot be + encoded without providing a storage root. + """ + return rlp.encode( + ( + raw_account_data.nonce, + raw_account_data.balance, + storage_root, + keccak256(raw_account_data.code), + ) + ) diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py new file mode 100644 index 0000000000..1cb9cdcdc7 --- /dev/null +++ b/src/ethereum/prague/state.py @@ -0,0 +1,734 @@ +""" +State +^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state contains all information that is preserved between transactions. + +It consists of a main account trie and storage tries for each contract. + +There is a distinction between an account that does not exist and +`EMPTY_ACCOUNT`. +""" +from dataclasses import dataclass, field +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple + +from ethereum_types.bytes import Bytes +from ethereum_types.frozen import modify +from ethereum_types.numeric import U256, Uint + +from .blocks import Withdrawal +from .fork_types import EMPTY_ACCOUNT, Account, Address, Root +from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set + + +@dataclass +class State: + """ + Contains all information that is preserved between transactions. + """ + + _main_trie: Trie[Address, Optional[Account]] = field( + default_factory=lambda: Trie(secured=True, default=None) + ) + _storage_tries: Dict[Address, Trie[Bytes, U256]] = field( + default_factory=dict + ) + _snapshots: List[ + Tuple[ + Trie[Address, Optional[Account]], Dict[Address, Trie[Bytes, U256]] + ] + ] = field(default_factory=list) + created_accounts: Set[Address] = field(default_factory=set) + + +@dataclass +class TransientStorage: + """ + Contains all information that is preserved between message calls + within a transaction. + """ + + _tries: Dict[Address, Trie[Bytes, U256]] = field(default_factory=dict) + _snapshots: List[Dict[Address, Trie[Bytes, U256]]] = field( + default_factory=list + ) + + +def close_state(state: State) -> None: + """ + Free resources held by the state. Used by optimized implementations to + release file descriptors. + """ + del state._main_trie + del state._storage_tries + del state._snapshots + del state.created_accounts + + +def begin_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Start a state transaction. + + Transactions are entirely implicit and can be nested. It is not possible to + calculate the state root during a transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.append( + ( + copy_trie(state._main_trie), + {k: copy_trie(t) for (k, t) in state._storage_tries.items()}, + ) + ) + transient_storage._snapshots.append( + {k: copy_trie(t) for (k, t) in transient_storage._tries.items()} + ) + + +def commit_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Commit a state transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._snapshots.pop() + + +def rollback_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Rollback a state transaction, resetting the state to the point when the + corresponding `start_transaction()` call was made. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._main_trie, state._storage_tries = state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._tries = transient_storage._snapshots.pop() + + +def get_account(state: State, address: Address) -> Account: + """ + Get the `Account` object at an address. Returns `EMPTY_ACCOUNT` if there + is no account at the address. + + Use `get_account_optional()` if you care about the difference between a + non-existent account and `EMPTY_ACCOUNT`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = get_account_optional(state, address) + if isinstance(account, Account): + return account + else: + return EMPTY_ACCOUNT + + +def get_account_optional(state: State, address: Address) -> Optional[Account]: + """ + Get the `Account` object at an address. Returns `None` (rather than + `EMPTY_ACCOUNT`) if there is no account at the address. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = trie_get(state._main_trie, address) + return account + + +def set_account( + state: State, address: Address, account: Optional[Account] +) -> None: + """ + Set the `Account` object at an address. Setting to `None` deletes + the account (but not its storage, see `destroy_account()`). + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to set. + account : `Account` + Account to set at address. + """ + trie_set(state._main_trie, address, account) + + +def destroy_account(state: State, address: Address) -> None: + """ + Completely remove the account at `address` and all of its storage. + + This function is made available exclusively for the `SELFDESTRUCT` + opcode. It is expected that `SELFDESTRUCT` will be disabled in a future + hardfork and this function will be removed. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account to destroy. + """ + destroy_storage(state, address) + set_account(state, address, None) + + +def destroy_storage(state: State, address: Address) -> None: + """ + Completely remove the storage at `address`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account whose storage is to be deleted. + """ + if address in state._storage_tries: + del state._storage_tries[address] + + +def mark_account_created(state: State, address: Address) -> None: + """ + Mark an account as having been created in the current transaction. + This information is used by `get_storage_original()` to handle an obscure + edgecase. + + The marker is not removed even if the account creation reverts. Since the + account cannot have had code prior to its creation and can't call + `get_storage_original()`, this is harmless. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account that has been created. + """ + state.created_accounts.add(address) + + +def get_storage(state: State, address: Address, key: Bytes) -> U256: + """ + Get a value at a storage key on an account. Returns `U256(0)` if the + storage key has not been set previously. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + + Returns + ------- + value : `U256` + Value at the key. + """ + trie = state._storage_tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_storage( + state: State, address: Address, key: Bytes, value: U256 +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + assert trie_get(state._main_trie, address) is not None + + trie = state._storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + state._storage_tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del state._storage_tries[address] + + +def storage_root(state: State, address: Address) -> Root: + """ + Calculate the storage root of an account. + + Parameters + ---------- + state: + The state + address : + Address of the account. + + Returns + ------- + root : `Root` + Storage root of the account. + """ + assert not state._snapshots + if address in state._storage_tries: + return root(state._storage_tries[address]) + else: + return EMPTY_TRIE_ROOT + + +def state_root(state: State) -> Root: + """ + Calculate the state root. + + Parameters + ---------- + state: + The current state. + + Returns + ------- + root : `Root` + The state root. + """ + assert not state._snapshots + + def get_storage_root(address: Address) -> Root: + return storage_root(state, address) + + return root(state._main_trie, get_storage_root=get_storage_root) + + +def account_exists(state: State, address: Address) -> bool: + """ + Checks if an account exists in the state trie + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + account_exists : `bool` + True if account exists in the state trie, False otherwise + """ + return get_account_optional(state, address) is not None + + +def account_has_code_or_nonce(state: State, address: Address) -> bool: + """ + Checks if an account has non zero nonce or non empty code + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_code_or_nonce : `bool` + True if the account has non zero nonce or non empty code, + False otherwise. + """ + account = get_account(state, address) + return account.nonce != Uint(0) or account.code != b"" + + +def account_has_storage(state: State, address: Address) -> bool: + """ + Checks if an account has storage. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_storage : `bool` + True if the account has storage, False otherwise. + """ + return address in state._storage_tries + + +def is_account_empty(state: State, address: Address) -> bool: + """ + Checks if an account has zero nonce, empty code and zero balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_empty : `bool` + True if if an account has zero nonce, empty code and zero balance, + False otherwise. + """ + account = get_account(state, address) + return ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def account_exists_and_is_empty(state: State, address: Address) -> bool: + """ + Checks if an account exists and has zero nonce, empty code and zero + balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + exists_and_is_empty : `bool` + True if an account exists and has zero nonce, empty code and zero + balance, False otherwise. + """ + account = get_account_optional(state, address) + return ( + account is not None + and account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def is_account_alive(state: State, address: Address) -> bool: + """ + Check whether is an account is both in the state and non empty. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_alive : `bool` + True if the account is alive. + """ + account = get_account_optional(state, address) + if account is None: + return False + else: + return not ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def modify_state( + state: State, address: Address, f: Callable[[Account], None] +) -> None: + """ + Modify an `Account` in the `State`. + """ + set_account(state, address, modify(get_account(state, address), f)) + + +def move_ether( + state: State, + sender_address: Address, + recipient_address: Address, + amount: U256, +) -> None: + """ + Move funds between accounts. + """ + + def reduce_sender_balance(sender: Account) -> None: + if sender.balance < amount: + raise AssertionError + sender.balance -= amount + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += amount + + modify_state(state, sender_address, reduce_sender_balance) + modify_state(state, recipient_address, increase_recipient_balance) + + +def process_withdrawal( + state: State, + wd: Withdrawal, +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + modify_state(state, wd.address, increase_recipient_balance) + + +def set_account_balance(state: State, address: Address, amount: U256) -> None: + """ + Sets the balance of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + + amount: + The amount that needs to set in balance. + """ + + def set_balance(account: Account) -> None: + account.balance = amount + + modify_state(state, address, set_balance) + + +def touch_account(state: State, address: Address) -> None: + """ + Initializes an account to state. + + Parameters + ---------- + state: + The current state. + + address: + The address of the account that need to initialised. + """ + if not account_exists(state, address): + set_account(state, address, EMPTY_ACCOUNT) + + +def increment_nonce(state: State, address: Address) -> None: + """ + Increments the nonce of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + """ + + def increase_nonce(sender: Account) -> None: + sender.nonce += Uint(1) + + modify_state(state, address, increase_nonce) + + +def set_code(state: State, address: Address, code: Bytes) -> None: + """ + Sets Account code. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose code needs to be update. + + code: + The bytecode that needs to be set. + """ + + def write_code(sender: Account) -> None: + sender.code = code + + modify_state(state, address, write_code) + + +def get_storage_original(state: State, address: Address, key: Bytes) -> U256: + """ + Get the original value in a storage slot i.e. the value before the current + transaction began. This function reads the value from the snapshots taken + before executing the transaction. + + Parameters + ---------- + state: + The current state. + address: + Address of the account to read the value from. + key: + Key of the storage slot. + """ + # In the transaction where an account is created, its preexisting storage + # is ignored. + if address in state.created_accounts: + return U256(0) + + _, original_trie = state._snapshots[0] + original_account_trie = original_trie.get(address) + + if original_account_trie is None: + original_value = U256(0) + else: + original_value = trie_get(original_account_trie, key) + + assert isinstance(original_value, U256) + + return original_value + + +def get_transient_storage( + transient_storage: TransientStorage, address: Address, key: Bytes +) -> U256: + """ + Get a value at a storage key on an account from transient storage. + Returns `U256(0)` if the storage key has not been set previously. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + Returns + ------- + value : `U256` + Value at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_transient_storage( + transient_storage: TransientStorage, + address: Address, + key: Bytes, + value: U256, +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + transient_storage._tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del transient_storage._tries[address] + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py new file mode 100644 index 0000000000..deb594c17c --- /dev/null +++ b/src/ethereum/prague/transactions.py @@ -0,0 +1,453 @@ +""" +Transactions are atomic units of work created externally to Ethereum and +submitted to be executed. If Ethereum is viewed as a state machine, +transactions are the events that move between states. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .exceptions import TransactionTypeError +from .fork_types import Address, VersionedHash + +TX_BASE_COST = 21000 +TX_DATA_COST_PER_NON_ZERO = 16 +TX_DATA_COST_PER_ZERO = 4 +TX_CREATE_COST = 32000 +TX_ACCESS_LIST_ADDRESS_COST = 2400 +TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 + + +@slotted_freezable +@dataclass +class LegacyTransaction: + """ + Atomic operation performed on the block chain. + """ + + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + v: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class AccessListTransaction: + """ + The transaction type added in EIP-2930 to support access lists. + """ + + chain_id: U64 + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class FeeMarketTransaction: + """ + The transaction type added in EIP-1559. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class BlobTransaction: + """ + The transaction type added in EIP-4844. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + max_fee_per_blob_gas: U256 + blob_versioned_hashes: Tuple[VersionedHash, ...] + y_parity: U256 + r: U256 + s: U256 + + +Transaction = Union[ + LegacyTransaction, + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, +] + + +def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]: + """ + Encode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, LegacyTransaction): + return tx + elif isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(tx) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(tx) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(tx) + else: + raise Exception(f"Unable to encode transaction of type {type(tx)}") + + +def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: + """ + Decode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, Bytes): + if tx[0] == 1: + return rlp.decode_to(AccessListTransaction, tx[1:]) + elif tx[0] == 2: + return rlp.decode_to(FeeMarketTransaction, tx[1:]) + elif tx[0] == 3: + return rlp.decode_to(BlobTransaction, tx[1:]) + else: + raise TransactionTypeError(tx[0]) + else: + return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + from .vm.interpreter import MAX_CODE_SIZE + + if calculate_intrinsic_cost(tx) > tx.gas: + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: + return False + + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + from .vm.gas import init_code_cost + + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance( + tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) + ): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + elif isinstance(tx, BlobTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_4844(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_4844(tx: BlobTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-4844 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x03" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.max_fee_per_blob_gas, + tx.blob_versioned_hashes, + ) + ) + ) diff --git a/src/ethereum/prague/trie.py b/src/ethereum/prague/trie.py new file mode 100644 index 0000000000..082cbb5495 --- /dev/null +++ b/src/ethereum/prague/trie.py @@ -0,0 +1,470 @@ +""" +State Trie +^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state trie is the structure responsible for storing +`.fork_types.Account` objects. +""" + +import copy +from dataclasses import dataclass, field +from typing import ( + Callable, + Dict, + Generic, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + TypeVar, + Union, +) + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.shanghai import trie as previous_trie +from ethereum.utils.hexadecimal import hex_to_bytes + +from .blocks import Receipt, Withdrawal +from .fork_types import Account, Address, Root, encode_account +from .transactions import LegacyTransaction + +# note: an empty trie (regardless of whether it is secured) has root: +# +# keccak256(RLP(b'')) +# == +# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 # noqa: E501,SC10 +# +# also: +# +# keccak256(RLP(())) +# == +# 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 # noqa: E501,SC10 +# +# which is the sha3Uncles hash in block header with no uncles +EMPTY_TRIE_ROOT = Root( + hex_to_bytes( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) +) + +Node = Union[ + Account, Bytes, LegacyTransaction, Receipt, Uint, U256, Withdrawal, None +] +K = TypeVar("K", bound=Bytes) +V = TypeVar( + "V", + Optional[Account], + Optional[Bytes], + Bytes, + Optional[Union[LegacyTransaction, Bytes]], + Optional[Union[Receipt, Bytes]], + Optional[Union[Withdrawal, Bytes]], + Uint, + U256, +) + + +@slotted_freezable +@dataclass +class LeafNode: + """Leaf node in the Merkle Trie""" + + rest_of_key: Bytes + value: rlp.Extended + + +@slotted_freezable +@dataclass +class ExtensionNode: + """Extension node in the Merkle Trie""" + + key_segment: Bytes + subnode: rlp.Extended + + +@slotted_freezable +@dataclass +class BranchNode: + """Branch node in the Merkle Trie""" + + subnodes: List[rlp.Extended] + value: rlp.Extended + + +InternalNode = Union[LeafNode, ExtensionNode, BranchNode] + + +def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: + """ + Encodes a Merkle Trie node into its RLP form. The RLP will then be + serialized into a `Bytes` and hashed unless it is less that 32 bytes + when serialized. + + This function also accepts `None`, representing the absence of a node, + which is encoded to `b""`. + + Parameters + ---------- + node : Optional[InternalNode] + The node to encode. + + Returns + ------- + encoded : `rlp.Extended` + The node encoded as RLP. + """ + unencoded: rlp.Extended + if node is None: + unencoded = b"" + elif isinstance(node, LeafNode): + unencoded = ( + nibble_list_to_compact(node.rest_of_key, True), + node.value, + ) + elif isinstance(node, ExtensionNode): + unencoded = ( + nibble_list_to_compact(node.key_segment, False), + node.subnode, + ) + elif isinstance(node, BranchNode): + unencoded = node.subnodes + [node.value] + else: + raise AssertionError(f"Invalid internal node type {type(node)}!") + + encoded = rlp.encode(unencoded) + if len(encoded) < 32: + return unencoded + else: + return keccak256(encoded) + + +def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: + """ + Encode a Node for storage in the Merkle Trie. + + Currently mostly an unimplemented stub. + """ + if isinstance(node, Account): + assert storage_root is not None + return encode_account(node, storage_root) + elif isinstance(node, (LegacyTransaction, Receipt, Withdrawal, U256)): + return rlp.encode(node) + elif isinstance(node, Bytes): + return node + else: + return previous_trie.encode_node(node, storage_root) + + +@dataclass +class Trie(Generic[K, V]): + """ + The Merkle Trie. + """ + + secured: bool + default: V + _data: Dict[K, V] = field(default_factory=dict) + + +def copy_trie(trie: Trie[K, V]) -> Trie[K, V]: + """ + Create a copy of `trie`. Since only frozen objects may be stored in tries, + the contents are reused. + + Parameters + ---------- + trie: `Trie` + Trie to copy. + + Returns + ------- + new_trie : `Trie[K, V]` + A copy of the trie. + """ + return Trie(trie.secured, trie.default, copy.copy(trie._data)) + + +def trie_set(trie: Trie[K, V], key: K, value: V) -> None: + """ + Stores an item in a Merkle Trie. + + This method deletes the key if `value == trie.default`, because the Merkle + Trie represents the default value by omitting it from the trie. + + Parameters + ---------- + trie: `Trie` + Trie to store in. + key : `Bytes` + Key to lookup. + value : `V` + Node to insert at `key`. + """ + if value == trie.default: + if key in trie._data: + del trie._data[key] + else: + trie._data[key] = value + + +def trie_get(trie: Trie[K, V], key: K) -> V: + """ + Gets an item from the Merkle Trie. + + This method returns `trie.default` if the key is missing. + + Parameters + ---------- + trie: + Trie to lookup in. + key : + Key to lookup. + + Returns + ------- + node : `V` + Node at `key` in the trie. + """ + return trie._data.get(key, trie.default) + + +def common_prefix_length(a: Sequence, b: Sequence) -> int: + """ + Find the longest common prefix of two sequences. + """ + for i in range(len(a)): + if i >= len(b) or a[i] != b[i]: + return i + return len(a) + + +def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes: + """ + Compresses nibble-list into a standard byte array with a flag. + + A nibble-list is a list of byte values no greater than `15`. The flag is + encoded in high nibble of the highest byte. The flag nibble can be broken + down into two two-bit flags. + + Highest nibble:: + + +---+---+----------+--------+ + | _ | _ | is_leaf | parity | + +---+---+----------+--------+ + 3 2 1 0 + + + The lowest bit of the nibble encodes the parity of the length of the + remaining nibbles -- `0` when even and `1` when odd. The second lowest bit + is used to distinguish leaf and extension nodes. The other two bits are not + used. + + Parameters + ---------- + x : + Array of nibbles. + is_leaf : + True if this is part of a leaf node, or false if it is an extension + node. + + Returns + ------- + compressed : `bytearray` + Compact byte array. + """ + compact = bytearray() + + if len(x) % 2 == 0: # ie even length + compact.append(16 * (2 * is_leaf)) + for i in range(0, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + else: + compact.append(16 * ((2 * is_leaf) + 1) + x[0]) + for i in range(1, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + + return Bytes(compact) + + +def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: + """ + Converts a `Bytes` into to a sequence of nibbles (bytes with value < 16). + + Parameters + ---------- + bytes_: + The `Bytes` to convert. + + Returns + ------- + nibble_list : `Bytes` + The `Bytes` in nibble-list format. + """ + nibble_list = bytearray(2 * len(bytes_)) + for byte_index, byte in enumerate(bytes_): + nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 + nibble_list[byte_index * 2 + 1] = byte & 0x0F + return Bytes(nibble_list) + + +def _prepare_trie( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Mapping[Bytes, Bytes]: + """ + Prepares the trie for root calculation. Removes values that are empty, + hashes the keys (if `secured == True`) and encodes all the nodes. + + Parameters + ---------- + trie : + The `Trie` to prepare. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + Returns + ------- + out : `Mapping[ethereum.base_types.Bytes, Node]` + Object with keys mapped to nibble-byte form. + """ + mapped: MutableMapping[Bytes, Bytes] = {} + + for preimage, value in trie._data.items(): + if isinstance(value, Account): + assert get_storage_root is not None + address = Address(preimage) + encoded_value = encode_node(value, get_storage_root(address)) + else: + encoded_value = encode_node(value) + if encoded_value == b"": + raise AssertionError + key: Bytes + if trie.secured: + # "secure" tries hash keys once before construction + key = keccak256(preimage) + else: + key = preimage + mapped[bytes_to_nibble_list(key)] = encoded_value + + return mapped + + +def root( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Root: + """ + Computes the root of a modified merkle patricia trie (MPT). + + Parameters + ---------- + trie : + `Trie` to get the root of. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + + Returns + ------- + root : `.fork_types.Root` + MPT root of the underlying key-value pairs. + """ + obj = _prepare_trie(trie, get_storage_root) + + root_node = encode_internal_node(patricialize(obj, Uint(0))) + if len(rlp.encode(root_node)) < 32: + return keccak256(rlp.encode(root_node)) + else: + assert isinstance(root_node, Bytes) + return Root(root_node) + + +def patricialize( + obj: Mapping[Bytes, Bytes], level: Uint +) -> Optional[InternalNode]: + """ + Structural composition function. + + Used to recursively patricialize and merkleize a dictionary. Includes + memoization of the tree structure and hashes. + + Parameters + ---------- + obj : + Underlying trie key-value pairs, with keys in nibble-list format. + level : + Current trie level. + + Returns + ------- + node : `ethereum.base_types.Bytes` + Root node of `obj`. + """ + if len(obj) == 0: + return None + + arbitrary_key = next(iter(obj)) + + # if leaf node + if len(obj) == 1: + leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) + return leaf + + # prepare for extension node check by finding max j such that all keys in + # obj have the same key[i:j] + substring = arbitrary_key[level:] + prefix_length = len(substring) + for key in obj: + prefix_length = min( + prefix_length, common_prefix_length(substring, key[level:]) + ) + + # finished searching, found another key at the current level + if prefix_length == 0: + break + + # if extension node + if prefix_length > 0: + prefix = arbitrary_key[int(level) : int(level) + prefix_length] + return ExtensionNode( + prefix, + encode_internal_node( + patricialize(obj, level + Uint(prefix_length)) + ), + ) + + branches: List[MutableMapping[Bytes, Bytes]] = [] + for _ in range(16): + branches.append({}) + value = b"" + for key in obj: + if len(key) == level: + # shouldn't ever have an account or receipt in an internal node + if isinstance(obj[key], (Account, Receipt, Uint)): + raise AssertionError + value = obj[key] + else: + branches[key[level]][key] = obj[key] + + return BranchNode( + [ + encode_internal_node(patricialize(branches[k], level + Uint(1))) + for k in range(16) + ], + value, + ) diff --git a/src/ethereum/prague/utils/__init__.py b/src/ethereum/prague/utils/__init__.py new file mode 100644 index 0000000000..224a4d269b --- /dev/null +++ b/src/ethereum/prague/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utility functions unique to this particular fork. +""" diff --git a/src/ethereum/prague/utils/address.py b/src/ethereum/prague/utils/address.py new file mode 100644 index 0000000000..3120d7a2c2 --- /dev/null +++ b/src/ethereum/prague/utils/address.py @@ -0,0 +1,93 @@ +""" +Hardfork Utility Functions For Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Address specific functions used in this prague version of +specification. +""" +from typing import Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.byte import left_pad_zero_bytes + +from ..fork_types import Address + + +def to_address(data: Union[Uint, U256]) -> Address: + """ + Convert a Uint or U256 value to a valid address (20 bytes). + + Parameters + ---------- + data : + The string to be converted to bytes. + + Returns + ------- + address : `Address` + The obtained address. + """ + return Address(data.to_be_bytes32()[-20:]) + + +def compute_contract_address(address: Address, nonce: Uint) -> Address: + """ + Computes address of the new account that needs to be created. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + nonce : + The transaction count of the account that wants to create the new + account. + + Returns + ------- + address: `Address` + The computed address of the new account. + """ + computed_address = keccak256(rlp.encode([address, nonce])) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + return Address(padded_address) + + +def compute_create2_contract_address( + address: Address, salt: Bytes32, call_data: bytearray +) -> Address: + """ + Computes address of the new account that needs to be created, which is + based on the sender address, salt and the call data as well. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + salt : + Address generation salt. + call_data : + The code of the new account which is to be created. + + Returns + ------- + address: `ethereum.prague.fork_types.Address` + The computed address of the new account. + """ + preimage = b"\xff" + address + salt + keccak256(call_data) + computed_address = keccak256(preimage) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + + return Address(padded_address) diff --git a/src/ethereum/prague/utils/hexadecimal.py b/src/ethereum/prague/utils/hexadecimal.py new file mode 100644 index 0000000000..5d6090084c --- /dev/null +++ b/src/ethereum/prague/utils/hexadecimal.py @@ -0,0 +1,68 @@ +""" +Utility Functions For Hexadecimal Strings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Hexadecimal utility functions used in this specification, specific to +Prague types. +""" +from ethereum.utils.hexadecimal import remove_hex_prefix + +from ..fork_types import Address, Bloom, Root + + +def hex_to_root(hex_string: str) -> Root: + """ + Convert hex string to trie root. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to trie root. + + Returns + ------- + root : `Root` + Trie root obtained from the given hexadecimal string. + """ + return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_bloom(hex_string: str) -> Bloom: + """ + Convert hex string to bloom. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to bloom. + + Returns + ------- + bloom : `Bloom` + Bloom obtained from the given hexadecimal string. + """ + return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_address(hex_string: str) -> Address: + """ + Convert hex string to Address (20 bytes). + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to Address. + + Returns + ------- + address : `Address` + The address obtained from the given hexadecimal string. + """ + return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py new file mode 100644 index 0000000000..7e91a7872c --- /dev/null +++ b/src/ethereum/prague/utils/message.py @@ -0,0 +1,116 @@ +""" +Hardfork Utility Functions For The Message Data-structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Message specific functions used in this prague version of +specification. +""" +from typing import FrozenSet, Optional, Tuple, Union + +from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.numeric import U256, Uint + +from ..fork_types import Address +from ..state import get_account +from ..vm import Environment, Message +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from .address import compute_contract_address + + +def prepare_message( + caller: Address, + target: Union[Bytes0, Address], + value: U256, + data: Bytes, + gas: Uint, + env: Environment, + code_address: Optional[Address] = None, + should_transfer_value: bool = True, + is_static: bool = False, + preaccessed_addresses: FrozenSet[Address] = frozenset(), + preaccessed_storage_keys: FrozenSet[ + Tuple[(Address, Bytes32)] + ] = frozenset(), +) -> Message: + """ + Execute a transaction against the provided environment. + + Parameters + ---------- + caller : + Address which initiated the transaction + target : + Address whose code will be executed + value : + Value to be transferred. + data : + Array of bytes provided to the code in `target`. + gas : + Gas provided for the code in `target`. + env : + Environment for the Ethereum Virtual Machine. + code_address : + This is usually same as the `target` address except when an alternative + accounts code needs to be executed. + eg. `CALLCODE` calling a precompile. + should_transfer_value : + if True ETH should be transferred while executing a message call. + is_static: + if True then it prevents all state-changing operations from being + executed. + preaccessed_addresses: + Addresses that should be marked as accessed prior to the message call + preaccessed_storage_keys: + Storage keys that should be marked as accessed prior to the message + call + + Returns + ------- + message: `ethereum.prague.vm.Message` + Items containing contract creation or message call specific data. + """ + if isinstance(target, Bytes0): + current_target = compute_contract_address( + caller, + get_account(env.state, caller).nonce - Uint(1), + ) + msg_data = Bytes(b"") + code = data + elif isinstance(target, Address): + current_target = target + msg_data = data + code = get_account(env.state, target).code + if code_address is None: + code_address = target + else: + raise AssertionError("Target must be address or empty bytes") + + accessed_addresses = set() + accessed_addresses.add(current_target) + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) + + return Message( + caller=caller, + target=target, + gas=gas, + value=value, + data=msg_data, + code=code, + depth=Uint(0), + current_target=current_target, + code_address=code_address, + should_transfer_value=should_transfer_value, + is_static=is_static, + accessed_addresses=accessed_addresses, + accessed_storage_keys=set(preaccessed_storage_keys), + parent_evm=None, + ) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py new file mode 100644 index 0000000000..04bb7a353a --- /dev/null +++ b/src/ethereum/prague/vm/__init__.py @@ -0,0 +1,151 @@ +""" +Ethereum Virtual Machine (EVM) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The abstract computer which runs the code stored in an +`.fork_types.Account`. +""" + +from dataclasses import dataclass +from typing import List, Optional, Set, Tuple, Union + +from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import Hash32 + +from ..blocks import Log +from ..fork_types import Address, VersionedHash +from ..state import State, TransientStorage, account_exists_and_is_empty +from .precompiled_contracts import RIPEMD160_ADDRESS + +__all__ = ("Environment", "Evm", "Message") + + +@dataclass +class Environment: + """ + Items external to the virtual machine itself, provided by the environment. + """ + + caller: Address + block_hashes: List[Hash32] + origin: Address + coinbase: Address + number: Uint + base_fee_per_gas: Uint + gas_limit: Uint + gas_price: Uint + time: U256 + prev_randao: Bytes32 + state: State + chain_id: U64 + traces: List[dict] + excess_blob_gas: U64 + blob_versioned_hashes: Tuple[VersionedHash, ...] + transient_storage: TransientStorage + + +@dataclass +class Message: + """ + Items that are used by contract creation or message call. + """ + + caller: Address + target: Union[Bytes0, Address] + current_target: Address + gas: Uint + value: U256 + data: Bytes + code_address: Optional[Address] + code: Bytes + depth: Uint + should_transfer_value: bool + is_static: bool + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + parent_evm: Optional["Evm"] + + +@dataclass +class Evm: + """The internal state of the virtual machine.""" + + pc: Uint + stack: List[U256] + memory: bytearray + code: Bytes + gas_left: Uint + env: Environment + valid_jump_destinations: Set[Uint] + logs: Tuple[Log, ...] + refund_counter: int + running: bool + message: Message + output: Bytes + accounts_to_delete: Set[Address] + touched_accounts: Set[Address] + return_data: Bytes + error: Optional[Exception] + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + + +def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of a successful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + evm.gas_left += child_evm.gas_left + evm.logs += child_evm.logs + evm.refund_counter += child_evm.refund_counter + evm.accounts_to_delete.update(child_evm.accounts_to_delete) + evm.touched_accounts.update(child_evm.touched_accounts) + if account_exists_and_is_empty( + evm.env.state, child_evm.message.current_target + ): + evm.touched_accounts.add(child_evm.message.current_target) + evm.accessed_addresses.update(child_evm.accessed_addresses) + evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) + + +def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was + # cleared despite running out of gas. This is an obscure edge case that can + # only happen to a precompile. + # According to the general rules governing clearing of empty accounts, the + # touch should have been reverted. Due to client bugs, this event went + # unnoticed and 0x3 has been exempted from the rule that touches are + # reverted in order to preserve this historical behaviour. + if RIPEMD160_ADDRESS in child_evm.touched_accounts: + evm.touched_accounts.add(RIPEMD160_ADDRESS) + if child_evm.message.current_target == RIPEMD160_ADDRESS: + if account_exists_and_is_empty( + evm.env.state, child_evm.message.current_target + ): + evm.touched_accounts.add(RIPEMD160_ADDRESS) + evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/prague/vm/exceptions.py b/src/ethereum/prague/vm/exceptions.py new file mode 100644 index 0000000000..2a4f2d2f65 --- /dev/null +++ b/src/ethereum/prague/vm/exceptions.py @@ -0,0 +1,140 @@ +""" +Ethereum Virtual Machine (EVM) Exceptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Exceptions which cause the EVM to halt exceptionally. +""" + +from ethereum.exceptions import EthereumException + + +class ExceptionalHalt(EthereumException): + """ + Indicates that the EVM has experienced an exceptional halt. This causes + execution to immediately end with all gas being consumed. + """ + + +class Revert(EthereumException): + """ + Raised by the `REVERT` opcode. + + Unlike other EVM exceptions this does not result in the consumption of all + gas. + """ + + pass + + +class StackUnderflowError(ExceptionalHalt): + """ + Occurs when a pop is executed on an empty stack. + """ + + pass + + +class StackOverflowError(ExceptionalHalt): + """ + Occurs when a push is executed on a stack at max capacity. + """ + + pass + + +class OutOfGasError(ExceptionalHalt): + """ + Occurs when an operation costs more than the amount of gas left in the + frame. + """ + + pass + + +class InvalidOpcode(ExceptionalHalt): + """ + Raised when an invalid opcode is encountered. + """ + + code: int + + def __init__(self, code: int) -> None: + super().__init__(code) + self.code = code + + +class InvalidJumpDestError(ExceptionalHalt): + """ + Occurs when the destination of a jump operation doesn't meet any of the + following criteria: + + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + """ + + +class StackDepthLimitError(ExceptionalHalt): + """ + Raised when the message depth is greater than `1024` + """ + + pass + + +class WriteInStaticContext(ExceptionalHalt): + """ + Raised when an attempt is made to modify the state while operating inside + of a STATICCALL context. + """ + + pass + + +class OutOfBoundsRead(ExceptionalHalt): + """ + Raised when an attempt was made to read data beyond the + boundaries of the buffer. + """ + + pass + + +class InvalidParameter(ExceptionalHalt): + """ + Raised when invalid parameters are passed. + """ + + pass + + +class InvalidContractPrefix(ExceptionalHalt): + """ + Raised when the new contract code starts with 0xEF. + """ + + pass + + +class AddressCollision(ExceptionalHalt): + """ + Raised when the new contract address has a collision. + """ + + pass + + +class KZGProofError(ExceptionalHalt): + """ + Raised when the point evaluation precompile can't verify a proof. + """ + + pass diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py new file mode 100644 index 0000000000..cc3ccc7c6b --- /dev/null +++ b/src/ethereum/prague/vm/gas.py @@ -0,0 +1,362 @@ +""" +Ethereum Virtual Machine (EVM) Gas +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM gas constants and calculators. +""" +from dataclasses import dataclass +from typing import List, Tuple + +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.trace import GasAndRefund, evm_trace +from ethereum.utils.numeric import ceil32, taylor_exponential + +from ..blocks import Header +from ..transactions import BlobTransaction, Transaction +from . import Evm +from .exceptions import OutOfGasError + +GAS_JUMPDEST = Uint(1) +GAS_BASE = Uint(2) +GAS_VERY_LOW = Uint(3) +GAS_STORAGE_SET = Uint(20000) +GAS_STORAGE_UPDATE = Uint(5000) +GAS_STORAGE_CLEAR_REFUND = Uint(4800) +GAS_LOW = Uint(5) +GAS_MID = Uint(8) +GAS_HIGH = Uint(10) +GAS_EXPONENTIATION = Uint(10) +GAS_EXPONENTIATION_PER_BYTE = Uint(50) +GAS_MEMORY = Uint(3) +GAS_KECCAK256 = Uint(30) +GAS_KECCAK256_WORD = Uint(6) +GAS_COPY = Uint(3) +GAS_BLOCK_HASH = Uint(20) +GAS_LOG = Uint(375) +GAS_LOG_DATA = Uint(8) +GAS_LOG_TOPIC = Uint(375) +GAS_CREATE = Uint(32000) +GAS_CODE_DEPOSIT = Uint(200) +GAS_ZERO = Uint(0) +GAS_NEW_ACCOUNT = Uint(25000) +GAS_CALL_VALUE = Uint(9000) +GAS_CALL_STIPEND = Uint(2300) +GAS_SELF_DESTRUCT = Uint(5000) +GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000) +GAS_ECRECOVER = Uint(3000) +GAS_SHA256 = Uint(60) +GAS_SHA256_WORD = Uint(12) +GAS_RIPEMD160 = Uint(600) +GAS_RIPEMD160_WORD = Uint(120) +GAS_IDENTITY = Uint(15) +GAS_IDENTITY_WORD = Uint(3) +GAS_RETURN_DATA_COPY = Uint(3) +GAS_FAST_STEP = Uint(5) +GAS_BLAKE2_PER_ROUND = Uint(1) +GAS_COLD_SLOAD = Uint(2100) +GAS_COLD_ACCOUNT_ACCESS = Uint(2600) +GAS_WARM_ACCESS = Uint(100) +GAS_INIT_CODE_WORD_COST = Uint(2) +GAS_BLOBHASH_OPCODE = Uint(3) +GAS_POINT_EVALUATION = Uint(50000) + +TARGET_BLOB_GAS_PER_BLOCK = U64(393216) +GAS_PER_BLOB = Uint(2**17) +MIN_BLOB_GASPRICE = Uint(1) +BLOB_GASPRICE_UPDATE_FRACTION = Uint(3338477) + + +@dataclass +class ExtendMemory: + """ + Define the parameters for memory extension in opcodes + + `cost`: `ethereum.base_types.Uint` + The gas required to perform the extension + `expand_by`: `ethereum.base_types.Uint` + The size by which the memory will be extended + """ + + cost: Uint + expand_by: Uint + + +@dataclass +class MessageCallGas: + """ + Define the gas cost and stipend for executing the call opcodes. + + `cost`: `ethereum.base_types.Uint` + The non-refundable portion of gas reserved for executing the + call opcode. + `stipend`: `ethereum.base_types.Uint` + The portion of gas available to sub-calls that is refundable + if not consumed + """ + + cost: Uint + stipend: Uint + + +def charge_gas(evm: Evm, amount: Uint) -> None: + """ + Subtracts `amount` from `evm.gas_left`. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas the current operation requires. + + """ + evm_trace(evm, GasAndRefund(int(amount))) + + if evm.gas_left < amount: + raise OutOfGasError + else: + evm.gas_left -= amount + + +def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: + """ + Calculates the gas cost for allocating memory + to the smallest multiple of 32 bytes, + such that the allocated size is at least as big as the given size. + + Parameters + ---------- + size_in_bytes : + The size of the data in bytes. + + Returns + ------- + total_gas_cost : `ethereum.base_types.Uint` + The gas cost for storing data in memory. + """ + size_in_words = ceil32(size_in_bytes) // Uint(32) + linear_cost = size_in_words * GAS_MEMORY + quadratic_cost = size_in_words ** Uint(2) // Uint(512) + total_gas_cost = linear_cost + quadratic_cost + try: + return total_gas_cost + except ValueError: + raise OutOfGasError + + +def calculate_gas_extend_memory( + memory: bytearray, extensions: List[Tuple[U256, U256]] +) -> ExtendMemory: + """ + Calculates the gas amount to extend memory + + Parameters + ---------- + memory : + Memory contents of the EVM. + extensions: + List of extensions to be made to the memory. + Consists of a tuple of start position and size. + + Returns + ------- + extend_memory: `ExtendMemory` + """ + size_to_extend = Uint(0) + to_be_paid = Uint(0) + current_size = Uint(len(memory)) + for start_position, size in extensions: + if size == 0: + continue + before_size = ceil32(current_size) + after_size = ceil32(Uint(start_position) + Uint(size)) + if after_size <= before_size: + continue + + size_to_extend += after_size - before_size + already_paid = calculate_memory_gas_cost(before_size) + total_cost = calculate_memory_gas_cost(after_size) + to_be_paid += total_cost - already_paid + + current_size = after_size + + return ExtendMemory(to_be_paid, size_to_extend) + + +def calculate_message_call_gas( + value: U256, + gas: Uint, + gas_left: Uint, + memory_cost: Uint, + extra_gas: Uint, + call_stipend: Uint = GAS_CALL_STIPEND, +) -> MessageCallGas: + """ + Calculates the MessageCallGas (cost and stipend) for + executing call Opcodes. + + Parameters + ---------- + value: + The amount of `ETH` that needs to be transferred. + gas : + The amount of gas provided to the message-call. + gas_left : + The amount of gas left in the current frame. + memory_cost : + The amount needed to extend the memory in the current frame. + extra_gas : + The amount of gas needed for transferring value + creating a new + account inside a message call. + call_stipend : + The amount of stipend provided to a message call to execute code while + transferring value(ETH). + + Returns + ------- + message_call_gas: `MessageCallGas` + """ + call_stipend = Uint(0) if value == 0 else call_stipend + if gas_left < extra_gas + memory_cost: + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + gas = min(gas, max_message_call_gas(gas_left - memory_cost - extra_gas)) + + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + +def max_message_call_gas(gas: Uint) -> Uint: + """ + Calculates the maximum gas that is allowed for making a message call + + Parameters + ---------- + gas : + The amount of gas provided to the message-call. + + Returns + ------- + max_allowed_message_call_gas: `ethereum.base_types.Uint` + The maximum gas allowed for making the message-call. + """ + return gas - (gas // Uint(64)) + + +def init_code_cost(init_code_length: Uint) -> Uint: + """ + Calculates the gas to be charged for the init code in CREAT* + opcodes as well as create transactions. + + Parameters + ---------- + init_code_length : + The length of the init code provided to the opcode + or a create transaction + + Returns + ------- + init_code_gas: `ethereum.base_types.Uint` + The gas to be charged for the init code. + """ + return GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) // Uint(32) + + +def calculate_excess_blob_gas(parent_header: Header) -> U64: + """ + Calculated the excess blob gas for the current block based + on the gas used in the parent block. + + Parameters + ---------- + parent_header : + The parent block of the current block. + + Returns + ------- + excess_blob_gas: `ethereum.base_types.U64` + The excess blob gas for the current block. + """ + # At the fork block, these are defined as zero. + excess_blob_gas = U64(0) + blob_gas_used = U64(0) + + if isinstance(parent_header, Header): + # After the fork block, read them from the parent header. + excess_blob_gas = parent_header.excess_blob_gas + blob_gas_used = parent_header.blob_gas_used + + parent_blob_gas = excess_blob_gas + blob_gas_used + if parent_blob_gas < TARGET_BLOB_GAS_PER_BLOCK: + return U64(0) + else: + return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK + + +def calculate_total_blob_gas(tx: Transaction) -> Uint: + """ + Calculate the total blob gas for a transaction. + + Parameters + ---------- + tx : + The transaction for which the blob gas is to be calculated. + + Returns + ------- + total_blob_gas: `ethereum.base_types.Uint` + The total blob gas for the transaction. + """ + if isinstance(tx, BlobTransaction): + return GAS_PER_BLOB * Uint(len(tx.blob_versioned_hashes)) + else: + return Uint(0) + + +def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: + """ + Calculate the blob gasprice for a block. + + Parameters + ---------- + excess_blob_gas : + The excess blob gas for the block. + + Returns + ------- + blob_gasprice: `Uint` + The blob gasprice. + """ + return taylor_exponential( + MIN_BLOB_GASPRICE, + Uint(excess_blob_gas), + BLOB_GASPRICE_UPDATE_FRACTION, + ) + + +def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: + """ + Calculate the blob data fee for a transaction. + + Parameters + ---------- + excess_blob_gas : + The excess_blob_gas for the execution. + tx : + The transaction for which the blob data fee is to be calculated. + + Returns + ------- + data_fee: `Uint` + The blob data fee. + """ + return calculate_total_blob_gas(tx) * calculate_blob_gas_price( + excess_blob_gas + ) diff --git a/src/ethereum/prague/vm/instructions/__init__.py b/src/ethereum/prague/vm/instructions/__init__.py new file mode 100644 index 0000000000..b220581c72 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/__init__.py @@ -0,0 +1,366 @@ +""" +EVM Instruction Encoding (Opcodes) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Machine readable representations of EVM instructions, and a mapping to their +implementations. +""" + +import enum +from typing import Callable, Dict + +from . import arithmetic as arithmetic_instructions +from . import bitwise as bitwise_instructions +from . import block as block_instructions +from . import comparison as comparison_instructions +from . import control_flow as control_flow_instructions +from . import environment as environment_instructions +from . import keccak as keccak_instructions +from . import log as log_instructions +from . import memory as memory_instructions +from . import stack as stack_instructions +from . import storage as storage_instructions +from . import system as system_instructions + + +class Ops(enum.Enum): + """ + Enum for EVM Opcodes + """ + + # Arithmetic Ops + ADD = 0x01 + MUL = 0x02 + SUB = 0x03 + DIV = 0x04 + SDIV = 0x05 + MOD = 0x06 + SMOD = 0x07 + ADDMOD = 0x08 + MULMOD = 0x09 + EXP = 0x0A + SIGNEXTEND = 0x0B + + # Comparison Ops + LT = 0x10 + GT = 0x11 + SLT = 0x12 + SGT = 0x13 + EQ = 0x14 + ISZERO = 0x15 + + # Bitwise Ops + AND = 0x16 + OR = 0x17 + XOR = 0x18 + NOT = 0x19 + BYTE = 0x1A + SHL = 0x1B + SHR = 0x1C + SAR = 0x1D + + # Keccak Op + KECCAK = 0x20 + + # Environmental Ops + ADDRESS = 0x30 + BALANCE = 0x31 + ORIGIN = 0x32 + CALLER = 0x33 + CALLVALUE = 0x34 + CALLDATALOAD = 0x35 + CALLDATASIZE = 0x36 + CALLDATACOPY = 0x37 + CODESIZE = 0x38 + CODECOPY = 0x39 + GASPRICE = 0x3A + EXTCODESIZE = 0x3B + EXTCODECOPY = 0x3C + RETURNDATASIZE = 0x3D + RETURNDATACOPY = 0x3E + EXTCODEHASH = 0x3F + + # Block Ops + BLOCKHASH = 0x40 + COINBASE = 0x41 + TIMESTAMP = 0x42 + NUMBER = 0x43 + PREVRANDAO = 0x44 + GASLIMIT = 0x45 + CHAINID = 0x46 + SELFBALANCE = 0x47 + BASEFEE = 0x48 + BLOBHASH = 0x49 + BLOBBASEFEE = 0x4A + + # Control Flow Ops + STOP = 0x00 + JUMP = 0x56 + JUMPI = 0x57 + PC = 0x58 + GAS = 0x5A + JUMPDEST = 0x5B + + # Storage Ops + SLOAD = 0x54 + SSTORE = 0x55 + TLOAD = 0x5C + TSTORE = 0x5D + + # Pop Operation + POP = 0x50 + + # Push Operations + PUSH0 = 0x5F + PUSH1 = 0x60 + PUSH2 = 0x61 + PUSH3 = 0x62 + PUSH4 = 0x63 + PUSH5 = 0x64 + PUSH6 = 0x65 + PUSH7 = 0x66 + PUSH8 = 0x67 + PUSH9 = 0x68 + PUSH10 = 0x69 + PUSH11 = 0x6A + PUSH12 = 0x6B + PUSH13 = 0x6C + PUSH14 = 0x6D + PUSH15 = 0x6E + PUSH16 = 0x6F + PUSH17 = 0x70 + PUSH18 = 0x71 + PUSH19 = 0x72 + PUSH20 = 0x73 + PUSH21 = 0x74 + PUSH22 = 0x75 + PUSH23 = 0x76 + PUSH24 = 0x77 + PUSH25 = 0x78 + PUSH26 = 0x79 + PUSH27 = 0x7A + PUSH28 = 0x7B + PUSH29 = 0x7C + PUSH30 = 0x7D + PUSH31 = 0x7E + PUSH32 = 0x7F + + # Dup operations + DUP1 = 0x80 + DUP2 = 0x81 + DUP3 = 0x82 + DUP4 = 0x83 + DUP5 = 0x84 + DUP6 = 0x85 + DUP7 = 0x86 + DUP8 = 0x87 + DUP9 = 0x88 + DUP10 = 0x89 + DUP11 = 0x8A + DUP12 = 0x8B + DUP13 = 0x8C + DUP14 = 0x8D + DUP15 = 0x8E + DUP16 = 0x8F + + # Swap operations + SWAP1 = 0x90 + SWAP2 = 0x91 + SWAP3 = 0x92 + SWAP4 = 0x93 + SWAP5 = 0x94 + SWAP6 = 0x95 + SWAP7 = 0x96 + SWAP8 = 0x97 + SWAP9 = 0x98 + SWAP10 = 0x99 + SWAP11 = 0x9A + SWAP12 = 0x9B + SWAP13 = 0x9C + SWAP14 = 0x9D + SWAP15 = 0x9E + SWAP16 = 0x9F + + # Memory Operations + MLOAD = 0x51 + MSTORE = 0x52 + MSTORE8 = 0x53 + MSIZE = 0x59 + MCOPY = 0x5E + + # Log Operations + LOG0 = 0xA0 + LOG1 = 0xA1 + LOG2 = 0xA2 + LOG3 = 0xA3 + LOG4 = 0xA4 + + # System Operations + CREATE = 0xF0 + CALL = 0xF1 + CALLCODE = 0xF2 + RETURN = 0xF3 + DELEGATECALL = 0xF4 + CREATE2 = 0xF5 + STATICCALL = 0xFA + REVERT = 0xFD + SELFDESTRUCT = 0xFF + + +op_implementation: Dict[Ops, Callable] = { + Ops.STOP: control_flow_instructions.stop, + Ops.ADD: arithmetic_instructions.add, + Ops.MUL: arithmetic_instructions.mul, + Ops.SUB: arithmetic_instructions.sub, + Ops.DIV: arithmetic_instructions.div, + Ops.SDIV: arithmetic_instructions.sdiv, + Ops.MOD: arithmetic_instructions.mod, + Ops.SMOD: arithmetic_instructions.smod, + Ops.ADDMOD: arithmetic_instructions.addmod, + Ops.MULMOD: arithmetic_instructions.mulmod, + Ops.EXP: arithmetic_instructions.exp, + Ops.SIGNEXTEND: arithmetic_instructions.signextend, + Ops.LT: comparison_instructions.less_than, + Ops.GT: comparison_instructions.greater_than, + Ops.SLT: comparison_instructions.signed_less_than, + Ops.SGT: comparison_instructions.signed_greater_than, + Ops.EQ: comparison_instructions.equal, + Ops.ISZERO: comparison_instructions.is_zero, + Ops.AND: bitwise_instructions.bitwise_and, + Ops.OR: bitwise_instructions.bitwise_or, + Ops.XOR: bitwise_instructions.bitwise_xor, + Ops.NOT: bitwise_instructions.bitwise_not, + Ops.BYTE: bitwise_instructions.get_byte, + Ops.SHL: bitwise_instructions.bitwise_shl, + Ops.SHR: bitwise_instructions.bitwise_shr, + Ops.SAR: bitwise_instructions.bitwise_sar, + Ops.KECCAK: keccak_instructions.keccak, + Ops.SLOAD: storage_instructions.sload, + Ops.BLOCKHASH: block_instructions.block_hash, + Ops.COINBASE: block_instructions.coinbase, + Ops.TIMESTAMP: block_instructions.timestamp, + Ops.NUMBER: block_instructions.number, + Ops.PREVRANDAO: block_instructions.prev_randao, + Ops.GASLIMIT: block_instructions.gas_limit, + Ops.CHAINID: block_instructions.chain_id, + Ops.MLOAD: memory_instructions.mload, + Ops.MSTORE: memory_instructions.mstore, + Ops.MSTORE8: memory_instructions.mstore8, + Ops.MSIZE: memory_instructions.msize, + Ops.MCOPY: memory_instructions.mcopy, + Ops.ADDRESS: environment_instructions.address, + Ops.BALANCE: environment_instructions.balance, + Ops.ORIGIN: environment_instructions.origin, + Ops.CALLER: environment_instructions.caller, + Ops.CALLVALUE: environment_instructions.callvalue, + Ops.CALLDATALOAD: environment_instructions.calldataload, + Ops.CALLDATASIZE: environment_instructions.calldatasize, + Ops.CALLDATACOPY: environment_instructions.calldatacopy, + Ops.CODESIZE: environment_instructions.codesize, + Ops.CODECOPY: environment_instructions.codecopy, + Ops.GASPRICE: environment_instructions.gasprice, + Ops.EXTCODESIZE: environment_instructions.extcodesize, + Ops.EXTCODECOPY: environment_instructions.extcodecopy, + Ops.RETURNDATASIZE: environment_instructions.returndatasize, + Ops.RETURNDATACOPY: environment_instructions.returndatacopy, + Ops.EXTCODEHASH: environment_instructions.extcodehash, + Ops.SELFBALANCE: environment_instructions.self_balance, + Ops.BASEFEE: environment_instructions.base_fee, + Ops.BLOBHASH: environment_instructions.blob_hash, + Ops.BLOBBASEFEE: environment_instructions.blob_base_fee, + Ops.SSTORE: storage_instructions.sstore, + Ops.TLOAD: storage_instructions.tload, + Ops.TSTORE: storage_instructions.tstore, + Ops.JUMP: control_flow_instructions.jump, + Ops.JUMPI: control_flow_instructions.jumpi, + Ops.PC: control_flow_instructions.pc, + Ops.GAS: control_flow_instructions.gas_left, + Ops.JUMPDEST: control_flow_instructions.jumpdest, + Ops.POP: stack_instructions.pop, + Ops.PUSH0: stack_instructions.push0, + Ops.PUSH1: stack_instructions.push1, + Ops.PUSH2: stack_instructions.push2, + Ops.PUSH3: stack_instructions.push3, + Ops.PUSH4: stack_instructions.push4, + Ops.PUSH5: stack_instructions.push5, + Ops.PUSH6: stack_instructions.push6, + Ops.PUSH7: stack_instructions.push7, + Ops.PUSH8: stack_instructions.push8, + Ops.PUSH9: stack_instructions.push9, + Ops.PUSH10: stack_instructions.push10, + Ops.PUSH11: stack_instructions.push11, + Ops.PUSH12: stack_instructions.push12, + Ops.PUSH13: stack_instructions.push13, + Ops.PUSH14: stack_instructions.push14, + Ops.PUSH15: stack_instructions.push15, + Ops.PUSH16: stack_instructions.push16, + Ops.PUSH17: stack_instructions.push17, + Ops.PUSH18: stack_instructions.push18, + Ops.PUSH19: stack_instructions.push19, + Ops.PUSH20: stack_instructions.push20, + Ops.PUSH21: stack_instructions.push21, + Ops.PUSH22: stack_instructions.push22, + Ops.PUSH23: stack_instructions.push23, + Ops.PUSH24: stack_instructions.push24, + Ops.PUSH25: stack_instructions.push25, + Ops.PUSH26: stack_instructions.push26, + Ops.PUSH27: stack_instructions.push27, + Ops.PUSH28: stack_instructions.push28, + Ops.PUSH29: stack_instructions.push29, + Ops.PUSH30: stack_instructions.push30, + Ops.PUSH31: stack_instructions.push31, + Ops.PUSH32: stack_instructions.push32, + Ops.DUP1: stack_instructions.dup1, + Ops.DUP2: stack_instructions.dup2, + Ops.DUP3: stack_instructions.dup3, + Ops.DUP4: stack_instructions.dup4, + Ops.DUP5: stack_instructions.dup5, + Ops.DUP6: stack_instructions.dup6, + Ops.DUP7: stack_instructions.dup7, + Ops.DUP8: stack_instructions.dup8, + Ops.DUP9: stack_instructions.dup9, + Ops.DUP10: stack_instructions.dup10, + Ops.DUP11: stack_instructions.dup11, + Ops.DUP12: stack_instructions.dup12, + Ops.DUP13: stack_instructions.dup13, + Ops.DUP14: stack_instructions.dup14, + Ops.DUP15: stack_instructions.dup15, + Ops.DUP16: stack_instructions.dup16, + Ops.SWAP1: stack_instructions.swap1, + Ops.SWAP2: stack_instructions.swap2, + Ops.SWAP3: stack_instructions.swap3, + Ops.SWAP4: stack_instructions.swap4, + Ops.SWAP5: stack_instructions.swap5, + Ops.SWAP6: stack_instructions.swap6, + Ops.SWAP7: stack_instructions.swap7, + Ops.SWAP8: stack_instructions.swap8, + Ops.SWAP9: stack_instructions.swap9, + Ops.SWAP10: stack_instructions.swap10, + Ops.SWAP11: stack_instructions.swap11, + Ops.SWAP12: stack_instructions.swap12, + Ops.SWAP13: stack_instructions.swap13, + Ops.SWAP14: stack_instructions.swap14, + Ops.SWAP15: stack_instructions.swap15, + Ops.SWAP16: stack_instructions.swap16, + Ops.LOG0: log_instructions.log0, + Ops.LOG1: log_instructions.log1, + Ops.LOG2: log_instructions.log2, + Ops.LOG3: log_instructions.log3, + Ops.LOG4: log_instructions.log4, + Ops.CREATE: system_instructions.create, + Ops.RETURN: system_instructions.return_, + Ops.CALL: system_instructions.call, + Ops.CALLCODE: system_instructions.callcode, + Ops.DELEGATECALL: system_instructions.delegatecall, + Ops.SELFDESTRUCT: system_instructions.selfdestruct, + Ops.STATICCALL: system_instructions.staticcall, + Ops.REVERT: system_instructions.revert, + Ops.CREATE2: system_instructions.create2, +} diff --git a/src/ethereum/prague/vm/instructions/arithmetic.py b/src/ethereum/prague/vm/instructions/arithmetic.py new file mode 100644 index 0000000000..0b8df99543 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/arithmetic.py @@ -0,0 +1,373 @@ +""" +Ethereum Virtual Machine (EVM) Arithmetic Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Arithmetic instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import get_sign + +from .. import Evm +from ..gas import ( + GAS_EXPONENTIATION, + GAS_EXPONENTIATION_PER_BYTE, + GAS_LOW, + GAS_MID, + GAS_VERY_LOW, + charge_gas, +) +from ..stack import pop, push + + +def add(evm: Evm) -> None: + """ + Adds the top two elements of the stack together, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_add(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sub(evm: Evm) -> None: + """ + Subtracts the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_sub(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mul(evm: Evm) -> None: + """ + Multiply the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + result = x.wrapping_mul(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def div(evm: Evm) -> None: + """ + Integer division of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack) + divisor = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = U256(0) + else: + quotient = dividend // divisor + + push(evm.stack, quotient) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +U255_CEIL_VALUE = 2**255 + + +def sdiv(evm: Evm) -> None: + """ + Signed integer division of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack).to_signed() + divisor = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = 0 + elif dividend == -U255_CEIL_VALUE and divisor == -1: + quotient = -U255_CEIL_VALUE + else: + sign = get_sign(dividend * divisor) + quotient = sign * (abs(dividend) // abs(divisor)) + + push(evm.stack, U256.from_signed(quotient)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mod(evm: Evm) -> None: + """ + Modulo remainder of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = U256(0) + else: + remainder = x % y + + push(evm.stack, remainder) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def smod(evm: Evm) -> None: + """ + Signed modulo remainder of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack).to_signed() + y = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = 0 + else: + remainder = get_sign(x) * (abs(x) % abs(y)) + + push(evm.stack, U256.from_signed(remainder)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def addmod(evm: Evm) -> None: + """ + Modulo addition of the top 2 elements with the 3rd element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x + y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mulmod(evm: Evm) -> None: + """ + Modulo multiplication of the top 2 elements with the 3rd element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x * y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def exp(evm: Evm) -> None: + """ + Exponential operation of the top 2 elements. Pushes the result back on + the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + base = Uint(pop(evm.stack)) + exponent = Uint(pop(evm.stack)) + + # GAS + # This is equivalent to 1 + floor(log(y, 256)). But in python the log + # function is inaccurate leading to wrong results. + exponent_bits = exponent.bit_length() + exponent_bytes = (exponent_bits + Uint(7)) // Uint(8) + charge_gas( + evm, GAS_EXPONENTIATION + GAS_EXPONENTIATION_PER_BYTE * exponent_bytes + ) + + # OPERATION + result = U256(pow(base, exponent, Uint(U256.MAX_VALUE) + Uint(1))) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signextend(evm: Evm) -> None: + """ + Sign extend operation. In other words, extend a signed number which + fits in N bytes to 32 bytes. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_num = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if byte_num > U256(31): + # Can't extend any further + result = value + else: + # U256(0).to_be_bytes() gives b'' instead b'\x00'. + value_bytes = bytes(value.to_be_bytes32()) + # Now among the obtained value bytes, consider only + # N `least significant bytes`, where N is `byte_num + 1`. + value_bytes = value_bytes[31 - int(byte_num) :] + sign_bit = value_bytes[0] >> 7 + if sign_bit == 0: + result = U256.from_be_bytes(value_bytes) + else: + num_bytes_prepend = U256(32) - (byte_num + U256(1)) + result = U256.from_be_bytes( + bytearray([0xFF] * num_bytes_prepend) + value_bytes + ) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/bitwise.py b/src/ethereum/prague/vm/instructions/bitwise.py new file mode 100644 index 0000000000..3abb58be48 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/bitwise.py @@ -0,0 +1,240 @@ +""" +Ethereum Virtual Machine (EVM) Bitwise Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM bitwise instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def bitwise_and(evm: Evm) -> None: + """ + Bitwise AND operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x & y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_or(evm: Evm) -> None: + """ + Bitwise OR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x | y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_xor(evm: Evm) -> None: + """ + Bitwise XOR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x ^ y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_not(evm: Evm) -> None: + """ + Bitwise NOT operation of the top element of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, ~x) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def get_byte(evm: Evm) -> None: + """ + For a word (defined by next top element of the stack), retrieve the + Nth byte (0-indexed and defined by top element of stack) from the + left (most significant) to right (least significant). + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_index = pop(evm.stack) + word = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if byte_index >= U256(32): + result = U256(0) + else: + extra_bytes_to_right = U256(31) - byte_index + # Remove the extra bytes in the right + word = word >> (extra_bytes_to_right * U256(8)) + # Remove the extra bytes in the left + word = word & U256(0xFF) + result = word + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shl(evm: Evm) -> None: + """ + Logical shift left (SHL) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = Uint(pop(evm.stack)) + value = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < Uint(256): + result = U256((value << shift) & Uint(U256.MAX_VALUE)) + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shr(evm: Evm) -> None: + """ + Logical shift right (SHR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < U256(256): + result = value >> shift + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_sar(evm: Evm) -> None: + """ + Arithmetic shift right (SAR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = int(pop(evm.stack)) + signed_value = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < 256: + result = U256.from_signed(signed_value >> shift) + elif signed_value >= 0: + result = U256(0) + else: + result = U256.MAX_VALUE + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/block.py b/src/ethereum/prague/vm/instructions/block.py new file mode 100644 index 0000000000..b4f7b556d7 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/block.py @@ -0,0 +1,249 @@ +""" +Ethereum Virtual Machine (EVM) Block Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM block instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas +from ..stack import pop, push + + +def block_hash(evm: Evm) -> None: + """ + Push the hash of one of the 256 most recent complete blocks onto the + stack. The block number to hash is present at the top of the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackUnderflowError` + If `len(stack)` is less than `1`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `20`. + """ + # STACK + block_number = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_BLOCK_HASH) + + # OPERATION + max_block_number = block_number + Uint(256) + if evm.env.number <= block_number or evm.env.number > max_block_number: + # Default hash to 0, if the block of interest is not yet on the chain + # (including the block which has the current executing transaction), + # or if the block's age is more than 256. + hash = b"\x00" + else: + hash = evm.env.block_hashes[-(evm.env.number - block_number)] + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def coinbase(evm: Evm) -> None: + """ + Push the current block's beneficiary address (address of the block miner) + onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def timestamp(evm: Evm) -> None: + """ + Push the current block's timestamp onto the stack. Here the timestamp + being referred is actually the unix timestamp in seconds. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.env.time) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def number(evm: Evm) -> None: + """ + Push the current block's number onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.env.number)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def prev_randao(evm: Evm) -> None: + """ + Push the `prev_randao` value onto the stack. + + The `prev_randao` value is the random output of the beacon chain's + randomness oracle for the previous block. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.env.prev_randao)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_limit(evm: Evm) -> None: + """ + Push the current block's gas limit onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.env.gas_limit)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def chain_id(evm: Evm) -> None: + """ + Push the chain id onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.prague.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.prague.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.env.chain_id)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/comparison.py b/src/ethereum/prague/vm/instructions/comparison.py new file mode 100644 index 0000000000..275455ba53 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/comparison.py @@ -0,0 +1,178 @@ +""" +Ethereum Virtual Machine (EVM) Comparison Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Comparison instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def less_than(evm: Evm) -> None: + """ + Checks if the top element is less than the next top element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_less_than(evm: Evm) -> None: + """ + Signed less-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def greater_than(evm: Evm) -> None: + """ + Checks if the top element is greater than the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_greater_than(evm: Evm) -> None: + """ + Signed greater-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def equal(evm: Evm) -> None: + """ + Checks if the top element is equal to the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left == right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def is_zero(evm: Evm) -> None: + """ + Checks if the top element is equal to 0. Pushes the result back on the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(x == 0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/control_flow.py b/src/ethereum/prague/vm/instructions/control_flow.py new file mode 100644 index 0000000000..7722661f79 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/control_flow.py @@ -0,0 +1,171 @@ +""" +Ethereum Virtual Machine (EVM) Control Flow Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM control flow instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ...vm.gas import GAS_BASE, GAS_HIGH, GAS_JUMPDEST, GAS_MID, charge_gas +from .. import Evm +from ..exceptions import InvalidJumpDestError +from ..stack import pop, push + + +def stop(evm: Evm) -> None: + """ + Stop further execution of EVM code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + pass + + # OPERATION + evm.running = False + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jump(evm: Evm) -> None: + """ + Alter the program counter to the location specified by the top of the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + + # PROGRAM COUNTER + evm.pc = Uint(jump_dest) + + +def jumpi(evm: Evm) -> None: + """ + Alter the program counter to the specified location if and only if a + condition is true. If the condition is not true, then the program counter + would increase only by 1. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + conditional_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_HIGH) + + # OPERATION + if conditional_value == 0: + destination = evm.pc + Uint(1) + elif jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + else: + destination = jump_dest + + # PROGRAM COUNTER + evm.pc = destination + + +def pc(evm: Evm) -> None: + """ + Push onto the stack the value of the program counter after reaching the + current instruction and without increasing it for the next instruction. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.pc)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_left(evm: Evm) -> None: + """ + Push the amount of available gas (including the corresponding reduction + for the cost of this instruction) onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.gas_left)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jumpdest(evm: Evm) -> None: + """ + Mark a valid destination for jumps. This is a noop, present only + to be used by `JUMP` and `JUMPI` opcodes to verify that their jump is + valid. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_JUMPDEST) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/environment.py b/src/ethereum/prague/vm/instructions/environment.py new file mode 100644 index 0000000000..4ba384f9ba --- /dev/null +++ b/src/ethereum/prague/vm/instructions/environment.py @@ -0,0 +1,587 @@ +""" +Ethereum Virtual Machine (EVM) Environmental Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM environment related instructions. +""" + +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from ...fork_types import EMPTY_ACCOUNT +from ...state import get_account +from ...utils.address import to_address +from ...vm.memory import buffer_read, memory_write +from .. import Evm +from ..exceptions import OutOfBoundsRead +from ..gas import ( + GAS_BASE, + GAS_BLOBHASH_OPCODE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_COPY, + GAS_FAST_STEP, + GAS_RETURN_DATA_COPY, + GAS_VERY_LOW, + GAS_WARM_ACCESS, + calculate_blob_gas_price, + calculate_gas_extend_memory, + charge_gas, +) +from ..stack import pop, push + + +def address(evm: Evm) -> None: + """ + Pushes the address of the current executing account to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.current_target)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def balance(evm: Evm) -> None: + """ + Pushes the balance of the given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_addresses.add(address) + charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account(evm.env.state, address).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def origin(evm: Evm) -> None: + """ + Pushes the address of the original transaction sender to the stack. + The origin address can only be an EOA. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.env.origin)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def caller(evm: Evm) -> None: + """ + Pushes the address of the caller onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.caller)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callvalue(evm: Evm) -> None: + """ + Push the value (in wei) sent with the call onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.message.value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldataload(evm: Evm) -> None: + """ + Push a word (32 bytes) of the input data belonging to the current + environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + value = buffer_read(evm.message.data, start_index, U256(32)) + + push(evm.stack, U256.from_be_bytes(value)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatasize(evm: Evm) -> None: + """ + Push the size of input data in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.message.data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatacopy(evm: Evm) -> None: + """ + Copy a portion of the input data in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + data_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.message.data, data_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codesize(evm: Evm) -> None: + """ + Push the size of code running in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.code))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codecopy(evm: Evm) -> None: + """ + Copy a portion of the code in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gasprice(evm: Evm) -> None: + """ + Push the gas price used in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.env.gas_price)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodesize(evm: Evm) -> None: + """ + Push the code size of a given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_addresses.add(address) + charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. + codesize = U256(len(get_account(evm.env.state, address).code)) + + push(evm.stack, codesize) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodecopy(evm: Evm) -> None: + """ + Copy a portion of an account's code to memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + else: + evm.accessed_addresses.add(address) + charge_gas( + evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatasize(evm: Evm) -> None: + """ + Pushes the size of the return data buffer onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.return_data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatacopy(evm: Evm) -> None: + """ + Copies data from the return data buffer code to memory + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + return_data_start_position = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_RETURN_DATA_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + if Uint(return_data_start_position) + Uint(size) > ulen(evm.return_data): + raise OutOfBoundsRead + + evm.memory += b"\x00" * extend_memory.expand_by + value = evm.return_data[ + return_data_start_position : return_data_start_position + size + ] + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodehash(evm: Evm) -> None: + """ + Returns the keccak256 hash of a contract’s bytecode + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_addresses.add(address) + charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + + # OPERATION + account = get_account(evm.env.state, address) + + if account == EMPTY_ACCOUNT: + codehash = U256(0) + else: + codehash = U256.from_be_bytes(keccak256(account.code)) + + push(evm.stack, codehash) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def self_balance(evm: Evm) -> None: + """ + Pushes the balance of the current address to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_FAST_STEP) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account(evm.env.state, evm.message.current_target).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def base_fee(evm: Evm) -> None: + """ + Pushes the base fee of the current block on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.env.base_fee_per_gas)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_hash(evm: Evm) -> None: + """ + Pushes the versioned hash at a particular index on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BLOBHASH_OPCODE) + + # OPERATION + if int(index) < len(evm.env.blob_versioned_hashes): + blob_hash = evm.env.blob_versioned_hashes[index] + else: + blob_hash = Bytes32(b"\x00" * 32) + push(evm.stack, U256.from_be_bytes(blob_hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_base_fee(evm: Evm) -> None: + """ + Pushes the blob base fee on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + blob_base_fee = calculate_blob_gas_price(evm.env.excess_blob_gas) + push(evm.stack, U256(blob_base_fee)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/keccak.py b/src/ethereum/prague/vm/instructions/keccak.py new file mode 100644 index 0000000000..830d368277 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/keccak.py @@ -0,0 +1,64 @@ +""" +Ethereum Virtual Machine (EVM) Keccak Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM keccak instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_KECCAK256, + GAS_KECCAK256_WORD, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop, push + + +def keccak(evm: Evm) -> None: + """ + Pushes to the stack the Keccak-256 hash of a region of memory. + + This also expands the memory, in case the memory is insufficient to + access the data's memory location. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + word_gas_cost = GAS_KECCAK256_WORD * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_KECCAK256 + word_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + data = memory_read_bytes(evm.memory, memory_start_index, size) + hash = keccak256(data) + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/log.py b/src/ethereum/prague/vm/instructions/log.py new file mode 100644 index 0000000000..87c06ed6be --- /dev/null +++ b/src/ethereum/prague/vm/instructions/log.py @@ -0,0 +1,88 @@ +""" +Ethereum Virtual Machine (EVM) Logging Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM logging instructions. +""" +from functools import partial + +from ethereum_types.numeric import Uint + +from ...blocks import Log +from .. import Evm +from ..exceptions import WriteInStaticContext +from ..gas import ( + GAS_LOG, + GAS_LOG_DATA, + GAS_LOG_TOPIC, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop + + +def log_n(evm: Evm, num_topics: int) -> None: + """ + Appends a log entry, having `num_topics` topics, to the evm logs. + + This will also expand the memory if the data (required by the log entry) + corresponding to the memory is not accessible. + + Parameters + ---------- + evm : + The current EVM frame. + num_topics : + The number of topics to be included in the log entry. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + topics = [] + for _ in range(num_topics): + topic = pop(evm.stack).to_be_bytes32() + topics.append(topic) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas( + evm, + GAS_LOG + + GAS_LOG_DATA * Uint(size) + + GAS_LOG_TOPIC * Uint(num_topics) + + extend_memory.cost, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + if evm.message.is_static: + raise WriteInStaticContext + log_entry = Log( + address=evm.message.current_target, + topics=tuple(topics), + data=memory_read_bytes(evm.memory, memory_start_index, size), + ) + + evm.logs = evm.logs + (log_entry,) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +log0 = partial(log_n, num_topics=0) +log1 = partial(log_n, num_topics=1) +log2 = partial(log_n, num_topics=2) +log3 = partial(log_n, num_topics=3) +log4 = partial(log_n, num_topics=4) diff --git a/src/ethereum/prague/vm/instructions/memory.py b/src/ethereum/prague/vm/instructions/memory.py new file mode 100644 index 0000000000..89533af37e --- /dev/null +++ b/src/ethereum/prague/vm/instructions/memory.py @@ -0,0 +1,177 @@ +""" +Ethereum Virtual Machine (EVM) Memory Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Memory instructions. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_BASE, + GAS_COPY, + GAS_VERY_LOW, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def mstore(evm: Evm) -> None: + """ + Stores a word to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(len(value)))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + memory_write(evm.memory, start_position, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mstore8(evm: Evm) -> None: + """ + Stores a byte to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(1))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + normalized_bytes_value = Bytes([value & U256(0xFF)]) + memory_write(evm.memory, start_position, normalized_bytes_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mload(evm: Evm) -> None: + """ + Load word from memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(32))] + ) + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = U256.from_be_bytes( + memory_read_bytes(evm.memory, start_position, U256(32)) + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def msize(evm: Evm) -> None: + """ + Push the size of active memory in bytes onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.memory))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mcopy(evm: Evm) -> None: + """ + Copy the bytes in memory from one location to another. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + destination = pop(evm.stack) + source = pop(evm.stack) + length = pop(evm.stack) + + # GAS + words = ceil32(Uint(length)) // Uint(32) + copy_gas_cost = GAS_COPY * words + + extend_memory = calculate_gas_extend_memory( + evm.memory, [(source, length), (destination, length)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = memory_read_bytes(evm.memory, source, length) + memory_write(evm.memory, destination, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/stack.py b/src/ethereum/prague/vm/instructions/stack.py new file mode 100644 index 0000000000..2e8a492412 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/stack.py @@ -0,0 +1,209 @@ +""" +Ethereum Virtual Machine (EVM) Stack Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM stack related instructions. +""" + +from functools import partial + +from ethereum_types.numeric import U256, Uint + +from .. import Evm, stack +from ..exceptions import StackUnderflowError +from ..gas import GAS_BASE, GAS_VERY_LOW, charge_gas +from ..memory import buffer_read + + +def pop(evm: Evm) -> None: + """ + Remove item from stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + stack.pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def push_n(evm: Evm, num_bytes: int) -> None: + """ + Pushes a N-byte immediate onto the stack. Push zero if num_bytes is zero. + + Parameters + ---------- + evm : + The current EVM frame. + + num_bytes : + The number of immediate bytes to be read from the code and pushed to + the stack. Push zero if num_bytes is zero. + + """ + # STACK + pass + + # GAS + if num_bytes == 0: + charge_gas(evm, GAS_BASE) + else: + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + data_to_push = U256.from_be_bytes( + buffer_read(evm.code, U256(evm.pc + Uint(1)), U256(num_bytes)) + ) + stack.push(evm.stack, data_to_push) + + # PROGRAM COUNTER + evm.pc += Uint(1) + Uint(num_bytes) + + +def dup_n(evm: Evm, item_number: int) -> None: + """ + Duplicate the Nth stack item (from top of the stack) to the top of stack. + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be duplicated + to the top of stack. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + data_to_duplicate = evm.stack[len(evm.stack) - 1 - item_number] + stack.push(evm.stack, data_to_duplicate) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def swap_n(evm: Evm, item_number: int) -> None: + """ + Swap the top and the `item_number` element of the stack, where + the top of the stack is position zero. + + If `item_number` is zero, this function does nothing (which should not be + possible, since there is no `SWAP0` instruction). + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be swapped + with the top of stack element. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + evm.stack[-1], evm.stack[-1 - item_number] = ( + evm.stack[-1 - item_number], + evm.stack[-1], + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +push0 = partial(push_n, num_bytes=0) +push1 = partial(push_n, num_bytes=1) +push2 = partial(push_n, num_bytes=2) +push3 = partial(push_n, num_bytes=3) +push4 = partial(push_n, num_bytes=4) +push5 = partial(push_n, num_bytes=5) +push6 = partial(push_n, num_bytes=6) +push7 = partial(push_n, num_bytes=7) +push8 = partial(push_n, num_bytes=8) +push9 = partial(push_n, num_bytes=9) +push10 = partial(push_n, num_bytes=10) +push11 = partial(push_n, num_bytes=11) +push12 = partial(push_n, num_bytes=12) +push13 = partial(push_n, num_bytes=13) +push14 = partial(push_n, num_bytes=14) +push15 = partial(push_n, num_bytes=15) +push16 = partial(push_n, num_bytes=16) +push17 = partial(push_n, num_bytes=17) +push18 = partial(push_n, num_bytes=18) +push19 = partial(push_n, num_bytes=19) +push20 = partial(push_n, num_bytes=20) +push21 = partial(push_n, num_bytes=21) +push22 = partial(push_n, num_bytes=22) +push23 = partial(push_n, num_bytes=23) +push24 = partial(push_n, num_bytes=24) +push25 = partial(push_n, num_bytes=25) +push26 = partial(push_n, num_bytes=26) +push27 = partial(push_n, num_bytes=27) +push28 = partial(push_n, num_bytes=28) +push29 = partial(push_n, num_bytes=29) +push30 = partial(push_n, num_bytes=30) +push31 = partial(push_n, num_bytes=31) +push32 = partial(push_n, num_bytes=32) + +dup1 = partial(dup_n, item_number=0) +dup2 = partial(dup_n, item_number=1) +dup3 = partial(dup_n, item_number=2) +dup4 = partial(dup_n, item_number=3) +dup5 = partial(dup_n, item_number=4) +dup6 = partial(dup_n, item_number=5) +dup7 = partial(dup_n, item_number=6) +dup8 = partial(dup_n, item_number=7) +dup9 = partial(dup_n, item_number=8) +dup10 = partial(dup_n, item_number=9) +dup11 = partial(dup_n, item_number=10) +dup12 = partial(dup_n, item_number=11) +dup13 = partial(dup_n, item_number=12) +dup14 = partial(dup_n, item_number=13) +dup15 = partial(dup_n, item_number=14) +dup16 = partial(dup_n, item_number=15) + +swap1 = partial(swap_n, item_number=1) +swap2 = partial(swap_n, item_number=2) +swap3 = partial(swap_n, item_number=3) +swap4 = partial(swap_n, item_number=4) +swap5 = partial(swap_n, item_number=5) +swap6 = partial(swap_n, item_number=6) +swap7 = partial(swap_n, item_number=7) +swap8 = partial(swap_n, item_number=8) +swap9 = partial(swap_n, item_number=9) +swap10 = partial(swap_n, item_number=10) +swap11 = partial(swap_n, item_number=11) +swap12 = partial(swap_n, item_number=12) +swap13 = partial(swap_n, item_number=13) +swap14 = partial(swap_n, item_number=14) +swap15 = partial(swap_n, item_number=15) +swap16 = partial(swap_n, item_number=16) diff --git a/src/ethereum/prague/vm/instructions/storage.py b/src/ethereum/prague/vm/instructions/storage.py new file mode 100644 index 0000000000..f88e295736 --- /dev/null +++ b/src/ethereum/prague/vm/instructions/storage.py @@ -0,0 +1,178 @@ +""" +Ethereum Virtual Machine (EVM) Storage Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM storage related instructions. +""" +from ethereum_types.numeric import Uint + +from ...state import ( + get_storage, + get_storage_original, + get_transient_storage, + set_storage, + set_transient_storage, +) +from .. import Evm +from ..exceptions import OutOfGasError, WriteInStaticContext +from ..gas import ( + GAS_CALL_STIPEND, + GAS_COLD_SLOAD, + GAS_STORAGE_CLEAR_REFUND, + GAS_STORAGE_SET, + GAS_STORAGE_UPDATE, + GAS_WARM_ACCESS, + charge_gas, +) +from ..stack import pop, push + + +def sload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + storage of the current account. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + if (evm.message.current_target, key) in evm.accessed_storage_keys: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + charge_gas(evm, GAS_COLD_SLOAD) + + # OPERATION + value = get_storage(evm.env.state, evm.message.current_target, key) + + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's storage. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + if evm.gas_left <= GAS_CALL_STIPEND: + raise OutOfGasError + + original_value = get_storage_original( + evm.env.state, evm.message.current_target, key + ) + current_value = get_storage(evm.env.state, evm.message.current_target, key) + + gas_cost = Uint(0) + + if (evm.message.current_target, key) not in evm.accessed_storage_keys: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + gas_cost += GAS_COLD_SLOAD + + if original_value == current_value and current_value != new_value: + if original_value == 0: + gas_cost += GAS_STORAGE_SET + else: + gas_cost += GAS_STORAGE_UPDATE - GAS_COLD_SLOAD + else: + gas_cost += GAS_WARM_ACCESS + + # Refund Counter Calculation + if current_value != new_value: + if original_value != 0 and current_value != 0 and new_value == 0: + # Storage is cleared for the first time in the transaction + evm.refund_counter += int(GAS_STORAGE_CLEAR_REFUND) + + if original_value != 0 and current_value == 0: + # Gas refund issued earlier to be reversed + evm.refund_counter -= int(GAS_STORAGE_CLEAR_REFUND) + + if original_value == new_value: + # Storage slot being restored to its original value + if original_value == 0: + # Slot was originally empty and was SET earlier + evm.refund_counter += int(GAS_STORAGE_SET - GAS_WARM_ACCESS) + else: + # Slot was originally non-empty and was UPDATED earlier + evm.refund_counter += int( + GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS + ) + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + set_storage(evm.env.state, evm.message.current_target, key, new_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + transient storage of the current account. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + + # OPERATION + value = get_transient_storage( + evm.env.transient_storage, evm.message.current_target, key + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's transient storage. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + if evm.message.is_static: + raise WriteInStaticContext + set_transient_storage( + evm.env.transient_storage, evm.message.current_target, key, new_value + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py new file mode 100644 index 0000000000..da47df38cf --- /dev/null +++ b/src/ethereum/prague/vm/instructions/system.py @@ -0,0 +1,691 @@ +""" +Ethereum Virtual Machine (EVM) System Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM system related instructions. +""" +from ethereum_types.bytes import Bytes0 +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import ceil32 + +from ...fork_types import Address +from ...state import ( + account_exists_and_is_empty, + 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, + to_address, +) +from .. import ( + Evm, + Message, + incorporate_child_on_error, + incorporate_child_on_success, +) +from ..exceptions import OutOfGasError, Revert, WriteInStaticContext +from ..gas import ( + GAS_CALL_VALUE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_CREATE, + GAS_KECCAK256_WORD, + GAS_NEW_ACCOUNT, + GAS_SELF_DESTRUCT, + GAS_SELF_DESTRUCT_NEW_ACCOUNT, + GAS_WARM_ACCESS, + GAS_ZERO, + calculate_gas_extend_memory, + calculate_message_call_gas, + charge_gas, + init_code_cost, + max_message_call_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def generic_create( + evm: Evm, + endowment: U256, + contract_address: Address, + memory_start_position: U256, + memory_size: U256, + init_code_gas: Uint, +) -> None: + """ + Core logic used by the `CREATE*` family of opcodes. + """ + # This import causes a circular import error + # if it's not moved inside this method + from ...vm.interpreter import ( + MAX_CODE_SIZE, + STACK_DEPTH_LIMIT, + process_create_message, + ) + + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + if len(call_data) > 2 * MAX_CODE_SIZE: + raise OutOfGasError + + evm.accessed_addresses.add(contract_address) + + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) + evm.gas_left -= create_message_gas + if evm.message.is_static: + raise WriteInStaticContext + evm.return_data = b"" + + sender_address = evm.message.current_target + sender = get_account(evm.env.state, sender_address) + + if ( + sender.balance < endowment + or sender.nonce == Uint(2**64 - 1) + or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT + ): + evm.gas_left += create_message_gas + push(evm.stack, U256(0)) + return + + if account_has_code_or_nonce( + evm.env.state, contract_address + ) or account_has_storage(evm.env.state, contract_address): + increment_nonce(evm.env.state, evm.message.current_target) + push(evm.stack, U256(0)) + return + + increment_nonce(evm.env.state, evm.message.current_target) + + child_message = Message( + caller=evm.message.current_target, + target=Bytes0(), + gas=create_message_gas, + value=endowment, + data=b"", + code=call_data, + current_target=contract_address, + depth=evm.message.depth + Uint(1), + code_address=None, + should_transfer_value=True, + is_static=False, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + parent_evm=evm, + ) + child_evm = process_create_message(child_message, evm.env) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = b"" + push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) + + +def create(evm: Evm) -> None: + """ + Creates a new account with associated code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + init_code_gas = init_code_cost(Uint(memory_size)) + + charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + contract_address = compute_contract_address( + evm.message.current_target, + get_account(evm.env.state, evm.message.current_target).nonce, + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + init_code_gas, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def create2(evm: Evm) -> None: + """ + Creates a new account with associated code. + + It's similar to CREATE opcode except that the address of new account + depends on the init_code instead of the nonce of sender. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + salt = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + call_data_words = ceil32(Uint(memory_size)) // Uint(32) + init_code_gas = init_code_cost(Uint(memory_size)) + charge_gas( + evm, + GAS_CREATE + + GAS_KECCAK256_WORD * call_data_words + + extend_memory.cost + + init_code_gas, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + contract_address = compute_create2_contract_address( + evm.message.current_target, + salt, + memory_read_bytes(evm.memory, memory_start_position, memory_size), + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + init_code_gas, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def return_(evm: Evm) -> None: + """ + Halts execution returning output data. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + + charge_gas(evm, GAS_ZERO + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + evm.output = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + + evm.running = False + + # PROGRAM COUNTER + pass + + +def generic_call( + evm: Evm, + gas: Uint, + value: U256, + caller: Address, + to: Address, + code_address: Address, + should_transfer_value: bool, + is_staticcall: bool, + memory_input_start_position: U256, + memory_input_size: U256, + memory_output_start_position: U256, + memory_output_size: U256, +) -> None: + """ + Perform the core logic of the `CALL*` family of opcodes. + """ + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message + + evm.return_data = b"" + + if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: + evm.gas_left += gas + push(evm.stack, U256(0)) + return + + call_data = memory_read_bytes( + evm.memory, memory_input_start_position, memory_input_size + ) + code = get_account(evm.env.state, code_address).code + child_message = Message( + caller=caller, + target=to, + gas=gas, + value=value, + data=call_data, + code=code, + current_target=to, + depth=evm.message.depth + Uint(1), + code_address=code_address, + should_transfer_value=should_transfer_value, + is_static=True if is_staticcall else evm.message.is_static, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + parent_evm=evm, + ) + child_evm = process_message(child_message, evm.env) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(1)) + + actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + memory_write( + evm.memory, + memory_output_start_position, + child_evm.output[:actual_output_size], + ) + + +def call(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + create_gas_cost = ( + Uint(0) + if is_account_alive(evm.env.state, to) or value == 0 + else GAS_NEW_ACCOUNT + ) + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + create_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + evm.memory += b"\x00" * extend_memory.expand_by + sender_balance = get_account( + evm.env.state, evm.message.current_target + ).balance + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + to, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callcode(evm: Evm) -> None: + """ + Message-call into this account with alternative account’s code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + to = evm.message.current_target + + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + sender_balance = get_account( + evm.env.state, evm.message.current_target + ).balance + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + code_address, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def selfdestruct(evm: Evm) -> None: + """ + Halt execution and register account for later deletion. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + beneficiary = to_address(pop(evm.stack)) + + # GAS + gas_cost = GAS_SELF_DESTRUCT + if beneficiary not in evm.accessed_addresses: + evm.accessed_addresses.add(beneficiary) + gas_cost += GAS_COLD_ACCOUNT_ACCESS + + if ( + not is_account_alive(evm.env.state, beneficiary) + and get_account(evm.env.state, evm.message.current_target).balance != 0 + ): + gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + + originator = evm.message.current_target + originator_balance = get_account(evm.env.state, originator).balance + + move_ether( + evm.env.state, + originator, + beneficiary, + originator_balance, + ) + + # register account for deletion only if it was created + # in the same transaction + if originator in evm.env.state.created_accounts: + # If beneficiary is the same as originator, then + # the ether is burnt. + set_account_balance(evm.env.state, originator, U256(0)) + evm.accounts_to_delete.add(originator) + + # mark beneficiary as touched + if account_exists_and_is_empty(evm.env.state, beneficiary): + evm.touched_accounts.add(beneficiary) + + # HALT the execution + evm.running = False + + # PROGRAM COUNTER + pass + + +def delegatecall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + message_call_gas = calculate_message_call_gas( + U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + evm.message.value, + evm.message.caller, + evm.message.current_target, + code_address, + False, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def staticcall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + message_call_gas = calculate_message_call_gas( + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + U256(0), + evm.message.current_target, + to, + to, + True, + True, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def revert(evm: Evm) -> None: + """ + Stop execution and revert state changes, without consuming all provided gas + and also has the ability to return a reason + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + charge_gas(evm, extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + output = memory_read_bytes(evm.memory, memory_start_index, size) + evm.output = bytes(output) + raise Revert + + # PROGRAM COUNTER + pass diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py new file mode 100644 index 0000000000..4a7c25a779 --- /dev/null +++ b/src/ethereum/prague/vm/interpreter.py @@ -0,0 +1,314 @@ +""" +Ethereum Virtual Machine (EVM) Interpreter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +A straightforward interpreter that executes EVM code. +""" +from dataclasses import dataclass +from typing import Iterable, Optional, Set, Tuple, Union + +from ethereum_types.bytes import Bytes0 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.trace import ( + EvmStop, + OpEnd, + OpException, + OpStart, + PrecompileEnd, + PrecompileStart, + TransactionEnd, + evm_trace, +) + +from ..blocks import Log +from ..fork_types import Address +from ..state import ( + account_exists_and_is_empty, + account_has_code_or_nonce, + account_has_storage, + begin_transaction, + commit_transaction, + destroy_storage, + increment_nonce, + mark_account_created, + move_ether, + rollback_transaction, + set_code, + touch_account, +) +from ..vm import Message +from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from . import Environment, Evm +from .exceptions import ( + AddressCollision, + ExceptionalHalt, + InvalidContractPrefix, + InvalidOpcode, + OutOfGasError, + Revert, + StackDepthLimitError, +) +from .instructions import Ops, op_implementation +from .runtime import get_valid_jump_destinations + +STACK_DEPTH_LIMIT = Uint(1024) +MAX_CODE_SIZE = 0x6000 + + +@dataclass +class MessageCallOutput: + """ + Output of a particular message call + + Contains the following: + + 1. `gas_left`: remaining gas after execution. + 2. `refund_counter`: gas to refund after execution. + 3. `logs`: list of `Log` generated during execution. + 4. `accounts_to_delete`: Contracts which have self-destructed. + 5. `touched_accounts`: Accounts that have been touched. + 6. `error`: The error from the execution if any. + """ + + gas_left: Uint + refund_counter: U256 + logs: Union[Tuple[()], Tuple[Log, ...]] + accounts_to_delete: Set[Address] + touched_accounts: Iterable[Address] + error: Optional[Exception] + + +def process_message_call( + message: Message, env: Environment +) -> MessageCallOutput: + """ + If `message.current` is empty then it creates a smart contract + else it executes a call from the `message.caller` to the `message.target`. + + Parameters + ---------- + message : + Transaction specific items. + + env : + External items required for EVM execution. + + Returns + ------- + output : `MessageCallOutput` + Output of the message call + """ + if message.target == Bytes0(b""): + is_collision = account_has_code_or_nonce( + env.state, message.current_target + ) or account_has_storage(env.state, message.current_target) + if is_collision: + return MessageCallOutput( + Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + ) + else: + evm = process_create_message(message, env) + else: + evm = process_message(message, env) + if account_exists_and_is_empty(env.state, Address(message.target)): + evm.touched_accounts.add(Address(message.target)) + + if evm.error: + logs: Tuple[Log, ...] = () + accounts_to_delete = set() + touched_accounts = set() + refund_counter = U256(0) + else: + logs = evm.logs + accounts_to_delete = evm.accounts_to_delete + touched_accounts = evm.touched_accounts + refund_counter = U256(evm.refund_counter) + + tx_end = TransactionEnd( + int(message.gas) - int(evm.gas_left), evm.output, evm.error + ) + evm_trace(evm, tx_end) + + return MessageCallOutput( + gas_left=evm.gas_left, + refund_counter=refund_counter, + logs=logs, + accounts_to_delete=accounts_to_delete, + touched_accounts=touched_accounts, + error=evm.error, + ) + + +def process_create_message(message: Message, env: Environment) -> Evm: + """ + Executes a call to create a smart contract. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: :py:class:`~ethereum.prague.vm.Evm` + Items containing execution specific objects. + """ + # take snapshot of state before processing the message + begin_transaction(env.state, env.transient_storage) + + # If the address where the account is being created has storage, it is + # destroyed. This can only happen in the following highly unlikely + # circumstances: + # * The address created by a `CREATE` call collides with a subsequent + # `CREATE` or `CREATE2` call. + # * The first `CREATE` happened before Spurious Dragon and left empty + # code. + destroy_storage(env.state, 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. + mark_account_created(env.state, message.current_target) + + increment_nonce(env.state, message.current_target) + evm = process_message(message, env) + if not evm.error: + contract_code = evm.output + contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT + try: + if len(contract_code) > 0: + if contract_code[0] == 0xEF: + raise InvalidContractPrefix + charge_gas(evm, contract_code_gas) + if len(contract_code) > MAX_CODE_SIZE: + raise OutOfGasError + except ExceptionalHalt as error: + rollback_transaction(env.state, env.transient_storage) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + else: + set_code(env.state, message.current_target, contract_code) + commit_transaction(env.state, env.transient_storage) + else: + rollback_transaction(env.state, env.transient_storage) + return evm + + +def process_message(message: Message, env: Environment) -> Evm: + """ + Executes a call to create a smart contract. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: :py:class:`~ethereum.prague.vm.Evm` + Items containing execution specific objects + """ + if message.depth > STACK_DEPTH_LIMIT: + raise StackDepthLimitError("Stack depth limit reached") + + # take snapshot of state before processing the message + begin_transaction(env.state, env.transient_storage) + + touch_account(env.state, message.current_target) + + if message.should_transfer_value and message.value != 0: + move_ether( + env.state, message.caller, message.current_target, message.value + ) + + evm = execute_code(message, env) + if evm.error: + # revert state to the last saved checkpoint + # since the message call resulted in an error + rollback_transaction(env.state, env.transient_storage) + else: + commit_transaction(env.state, env.transient_storage) + return evm + + +def execute_code(message: Message, env: Environment) -> Evm: + """ + Executes bytecode present in the `message`. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: `ethereum.vm.EVM` + Items containing execution specific objects + """ + code = message.code + valid_jump_destinations = get_valid_jump_destinations(code) + + evm = Evm( + pc=Uint(0), + stack=[], + memory=bytearray(), + code=code, + gas_left=message.gas, + env=env, + valid_jump_destinations=valid_jump_destinations, + logs=(), + refund_counter=0, + running=True, + message=message, + output=b"", + accounts_to_delete=set(), + touched_accounts=set(), + return_data=b"", + error=None, + accessed_addresses=message.accessed_addresses, + accessed_storage_keys=message.accessed_storage_keys, + ) + try: + if evm.message.code_address in PRE_COMPILED_CONTRACTS: + evm_trace(evm, PrecompileStart(evm.message.code_address)) + PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) + evm_trace(evm, PrecompileEnd()) + return evm + + while evm.running and evm.pc < ulen(evm.code): + try: + op = Ops(evm.code[evm.pc]) + except ValueError: + raise InvalidOpcode(evm.code[evm.pc]) + + evm_trace(evm, OpStart(op)) + op_implementation[op](evm) + evm_trace(evm, OpEnd()) + + evm_trace(evm, EvmStop(Ops.STOP)) + + except ExceptionalHalt as error: + evm_trace(evm, OpException(error)) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + except Revert as error: + evm_trace(evm, OpException(error)) + evm.error = error + return evm diff --git a/src/ethereum/prague/vm/memory.py b/src/ethereum/prague/vm/memory.py new file mode 100644 index 0000000000..aa2e7fdd57 --- /dev/null +++ b/src/ethereum/prague/vm/memory.py @@ -0,0 +1,81 @@ +""" +Ethereum Virtual Machine (EVM) Memory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM memory operations. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.byte import right_pad_zero_bytes + + +def memory_write( + memory: bytearray, start_position: U256, value: Bytes +) -> None: + """ + Writes to memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + value : + Data to write to memory. + """ + memory[start_position : int(start_position) + len(value)] = value + + +def memory_read_bytes( + memory: bytearray, start_position: U256, size: U256 +) -> bytearray: + """ + Read bytes from memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return memory[start_position : Uint(start_position) + Uint(size)] + + +def buffer_read(buffer: Bytes, start_position: U256, size: U256) -> Bytes: + """ + Read bytes from a buffer. Padding with zeros if necessary. + + Parameters + ---------- + buffer : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return right_pad_zero_bytes( + buffer[start_position : Uint(start_position) + Uint(size)], size + ) diff --git a/src/ethereum/prague/vm/precompiled_contracts/__init__.py b/src/ethereum/prague/vm/precompiled_contracts/__init__.py new file mode 100644 index 0000000000..7ec48ca69b --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/__init__.py @@ -0,0 +1,40 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Addresses of precompiled contracts and mappings to their +implementations. +""" + +from ...utils.hexadecimal import hex_to_address + +__all__ = ( + "ECRECOVER_ADDRESS", + "SHA256_ADDRESS", + "RIPEMD160_ADDRESS", + "IDENTITY_ADDRESS", + "MODEXP_ADDRESS", + "ALT_BN128_ADD_ADDRESS", + "ALT_BN128_MUL_ADDRESS", + "ALT_BN128_PAIRING_CHECK_ADDRESS", + "BLAKE2F_ADDRESS", + "POINT_EVALUATION_ADDRESS", +) + +ECRECOVER_ADDRESS = hex_to_address("0x01") +SHA256_ADDRESS = hex_to_address("0x02") +RIPEMD160_ADDRESS = hex_to_address("0x03") +IDENTITY_ADDRESS = hex_to_address("0x04") +MODEXP_ADDRESS = hex_to_address("0x05") +ALT_BN128_ADD_ADDRESS = hex_to_address("0x06") +ALT_BN128_MUL_ADDRESS = hex_to_address("0x07") +ALT_BN128_PAIRING_CHECK_ADDRESS = hex_to_address("0x08") +BLAKE2F_ADDRESS = hex_to_address("0x09") +POINT_EVALUATION_ADDRESS = hex_to_address("0x0a") diff --git a/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py new file mode 100644 index 0000000000..dc75b40ac6 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py @@ -0,0 +1,154 @@ +""" +Ethereum Virtual Machine (EVM) ALT_BN128 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ALT_BN128 precompiled contracts. +""" +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.alt_bn128 import ( + ALT_BN128_CURVE_ORDER, + ALT_BN128_PRIME, + BNF, + BNF2, + BNF12, + BNP, + BNP2, + pairing, +) + +from ...vm import Evm +from ...vm.gas import charge_gas +from ...vm.memory import buffer_read +from ..exceptions import OutOfGasError + + +def alt_bn128_add(evm: Evm) -> None: + """ + The ALT_BN128 addition precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(150)) + + # OPERATION + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0_value = int(U256.from_be_bytes(x0_bytes)) + y0_bytes = buffer_read(data, U256(32), U256(32)) + y0_value = int(U256.from_be_bytes(y0_bytes)) + x1_bytes = buffer_read(data, U256(64), U256(32)) + x1_value = int(U256.from_be_bytes(x1_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1_value = int(U256.from_be_bytes(y1_bytes)) + + for i in (x0_value, y0_value, x1_value, y1_value): + if i >= ALT_BN128_PRIME: + raise OutOfGasError + + try: + p0 = BNP(BNF(x0_value), BNF(y0_value)) + p1 = BNP(BNF(x1_value), BNF(y1_value)) + except ValueError: + raise OutOfGasError + + p = p0 + p1 + + evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + + +def alt_bn128_mul(evm: Evm) -> None: + """ + The ALT_BN128 multiplication precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(6000)) + + # OPERATION + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0_value = int(U256.from_be_bytes(x0_bytes)) + y0_bytes = buffer_read(data, U256(32), U256(32)) + y0_value = int(U256.from_be_bytes(y0_bytes)) + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) + + for i in (x0_value, y0_value): + if i >= ALT_BN128_PRIME: + raise OutOfGasError + + try: + p0 = BNP(BNF(x0_value), BNF(y0_value)) + except ValueError: + raise OutOfGasError + + p = p0.mul_by(n) + + evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + + +def alt_bn128_pairing_check(evm: Evm) -> None: + """ + The ALT_BN128 pairing check precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(34000 * (len(data) // 192) + 45000)) + + # OPERATION + if len(data) % 192 != 0: + raise OutOfGasError + result = BNF12.from_int(1) + for i in range(len(data) // 192): + values = [] + for j in range(6): + value = int( + U256.from_be_bytes( + data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] + ) + ) + if value >= ALT_BN128_PRIME: + raise OutOfGasError + values.append(value) + + try: + p = BNP(BNF(values[0]), BNF(values[1])) + q = BNP2( + BNF2((values[3], values[2])), BNF2((values[5], values[4])) + ) + except ValueError: + raise OutOfGasError() + if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + raise OutOfGasError + if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + raise OutOfGasError + if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): + result = result * pairing(q, p) + + if result == BNF12.from_int(1): + evm.output = U256(1).to_be_bytes32() + else: + evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/prague/vm/precompiled_contracts/blake2f.py b/src/ethereum/prague/vm/precompiled_contracts/blake2f.py new file mode 100644 index 0000000000..0d86ba6e85 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/blake2f.py @@ -0,0 +1,41 @@ +""" +Ethereum Virtual Machine (EVM) Blake2 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `Blake2` precompiled contract. +""" +from ethereum.crypto.blake2 import Blake2b + +from ...vm import Evm +from ...vm.gas import GAS_BLAKE2_PER_ROUND, charge_gas +from ..exceptions import InvalidParameter + + +def blake2f(evm: Evm) -> None: + """ + Writes the Blake2 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + if len(data) != 213: + raise InvalidParameter + + blake2b = Blake2b() + rounds, h, m, t_0, t_1, f = blake2b.get_blake2_parameters(data) + + charge_gas(evm, GAS_BLAKE2_PER_ROUND * rounds) + if f not in [0, 1]: + raise InvalidParameter + + evm.output = blake2b.compress(rounds, h, m, t_0, t_1, f) diff --git a/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py b/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py new file mode 100644 index 0000000000..293e977575 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py @@ -0,0 +1,62 @@ +""" +Ethereum Virtual Machine (EVM) ECRECOVER PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ECRECOVER precompiled contract. +""" +from ethereum_types.numeric import U256 + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.utils.byte import left_pad_zero_bytes + +from ...vm import Evm +from ...vm.gas import GAS_ECRECOVER, charge_gas +from ...vm.memory import buffer_read + + +def ecrecover(evm: Evm) -> None: + """ + Decrypts the address using elliptic curve DSA recovery mechanism and writes + the address to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, GAS_ECRECOVER) + + # OPERATION + message_hash_bytes = buffer_read(data, U256(0), U256(32)) + message_hash = Hash32(message_hash_bytes) + v = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + r = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + s = U256.from_be_bytes(buffer_read(data, U256(96), U256(32))) + + if v != U256(27) and v != U256(28): + return + if U256(0) >= r or r >= SECP256K1N: + return + if U256(0) >= s or s >= SECP256K1N: + return + + try: + public_key = secp256k1_recover(r, s, v - U256(27), message_hash) + except ValueError: + # unable to extract public key + return + + address = keccak256(public_key)[12:32] + padded_address = left_pad_zero_bytes(address, 32) + evm.output = padded_address diff --git a/src/ethereum/prague/vm/precompiled_contracts/identity.py b/src/ethereum/prague/vm/precompiled_contracts/identity.py new file mode 100644 index 0000000000..88729c96d7 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/identity.py @@ -0,0 +1,38 @@ +""" +Ethereum Virtual Machine (EVM) IDENTITY PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `IDENTITY` precompiled contract. +""" +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_IDENTITY, GAS_IDENTITY_WORD, charge_gas + + +def identity(evm: Evm) -> None: + """ + Writes the message data to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_IDENTITY + GAS_IDENTITY_WORD * word_count) + + # OPERATION + evm.output = data diff --git a/src/ethereum/prague/vm/precompiled_contracts/mapping.py b/src/ethereum/prague/vm/precompiled_contracts/mapping.py new file mode 100644 index 0000000000..7bd3416b6b --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/mapping.py @@ -0,0 +1,49 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Mapping of precompiled contracts their implementations. +""" +from typing import Callable, Dict + +from ...fork_types import Address +from . import ( + ALT_BN128_ADD_ADDRESS, + ALT_BN128_MUL_ADDRESS, + ALT_BN128_PAIRING_CHECK_ADDRESS, + BLAKE2F_ADDRESS, + ECRECOVER_ADDRESS, + IDENTITY_ADDRESS, + MODEXP_ADDRESS, + POINT_EVALUATION_ADDRESS, + RIPEMD160_ADDRESS, + SHA256_ADDRESS, +) +from .alt_bn128 import alt_bn128_add, alt_bn128_mul, alt_bn128_pairing_check +from .blake2f import blake2f +from .ecrecover import ecrecover +from .identity import identity +from .modexp import modexp +from .point_evaluation import point_evaluation +from .ripemd160 import ripemd160 +from .sha256 import sha256 + +PRE_COMPILED_CONTRACTS: Dict[Address, Callable] = { + ECRECOVER_ADDRESS: ecrecover, + SHA256_ADDRESS: sha256, + RIPEMD160_ADDRESS: ripemd160, + IDENTITY_ADDRESS: identity, + MODEXP_ADDRESS: modexp, + ALT_BN128_ADD_ADDRESS: alt_bn128_add, + ALT_BN128_MUL_ADDRESS: alt_bn128_mul, + ALT_BN128_PAIRING_CHECK_ADDRESS: alt_bn128_pairing_check, + BLAKE2F_ADDRESS: blake2f, + POINT_EVALUATION_ADDRESS: point_evaluation, +} diff --git a/src/ethereum/prague/vm/precompiled_contracts/modexp.py b/src/ethereum/prague/vm/precompiled_contracts/modexp.py new file mode 100644 index 0000000000..403fe86b11 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/modexp.py @@ -0,0 +1,169 @@ +""" +Ethereum Virtual Machine (EVM) MODEXP PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `MODEXP` precompiled contract. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ...vm import Evm +from ...vm.gas import charge_gas +from ..memory import buffer_read + +GQUADDIVISOR = Uint(3) + + +def modexp(evm: Evm) -> None: + """ + Calculates `(base**exp) % modulus` for arbitrary sized `base`, `exp` and. + `modulus`. The return value is the same length as the modulus. + """ + data = evm.message.data + + # GAS + base_length = U256.from_be_bytes(buffer_read(data, U256(0), U256(32))) + exp_length = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + modulus_length = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + + exp_start = U256(96) + base_length + + exp_head = Uint.from_be_bytes( + buffer_read(data, exp_start, min(U256(32), exp_length)) + ) + + charge_gas( + evm, + gas_cost(base_length, modulus_length, exp_length, exp_head), + ) + + # OPERATION + if base_length == 0 and modulus_length == 0: + evm.output = Bytes() + return + + base = Uint.from_be_bytes(buffer_read(data, U256(96), base_length)) + exp = Uint.from_be_bytes(buffer_read(data, exp_start, exp_length)) + + modulus_start = exp_start + exp_length + modulus = Uint.from_be_bytes( + buffer_read(data, modulus_start, modulus_length) + ) + + if modulus == 0: + evm.output = Bytes(b"\x00") * modulus_length + else: + evm.output = pow(base, exp, modulus).to_bytes( + Uint(modulus_length), "big" + ) + + +def complexity(base_length: U256, modulus_length: U256) -> Uint: + """ + Estimate the complexity of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + Returns + ------- + + complexity : `Uint` + Complexity of performing the operation. + """ + max_length = max(Uint(base_length), Uint(modulus_length)) + words = (max_length + Uint(7)) // Uint(8) + return words ** Uint(2) + + +def iterations(exponent_length: U256, exponent_head: Uint) -> Uint: + """ + Calculate the number of iterations required to perform a modular + exponentiation. + + Parameters + ---------- + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + iterations : `Uint` + Number of iterations. + """ + if exponent_length <= U256(32) and exponent_head == U256(0): + count = Uint(0) + elif exponent_length <= U256(32): + bit_length = Uint(exponent_head.bit_length()) + + if bit_length > Uint(0): + bit_length -= Uint(1) + + count = bit_length + else: + length_part = Uint(8) * (Uint(exponent_length) - Uint(32)) + bits_part = Uint(exponent_head.bit_length()) + + if bits_part > Uint(0): + bits_part -= Uint(1) + + count = length_part + bits_part + + return max(count, Uint(1)) + + +def gas_cost( + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent_head: Uint, +) -> Uint: + """ + Calculate the gas cost of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + gas_cost : `Uint` + Gas required for performing the operation. + """ + multiplication_complexity = complexity(base_length, modulus_length) + iteration_count = iterations(exponent_length, exponent_head) + cost = multiplication_complexity * iteration_count + cost //= GQUADDIVISOR + return max(Uint(200), cost) diff --git a/src/ethereum/prague/vm/precompiled_contracts/point_evaluation.py b/src/ethereum/prague/vm/precompiled_contracts/point_evaluation.py new file mode 100644 index 0000000000..188f90f83f --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/point_evaluation.py @@ -0,0 +1,72 @@ +""" +Ethereum Virtual Machine (EVM) POINT EVALUATION PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the POINT EVALUATION precompiled contract. +""" +from ethereum_types.bytes import Bytes, Bytes32, Bytes48 +from ethereum_types.numeric import U256 + +from ethereum.crypto.kzg import ( + KZGCommitment, + kzg_commitment_to_versioned_hash, + verify_kzg_proof, +) + +from ...vm import Evm +from ...vm.exceptions import KZGProofError +from ...vm.gas import GAS_POINT_EVALUATION, charge_gas + +FIELD_ELEMENTS_PER_BLOB = 4096 +BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513 # noqa: E501 +VERSIONED_HASH_VERSION_KZG = b"\x01" + + +def point_evaluation(evm: Evm) -> None: + """ + A pre-compile that verifies a KZG proof which claims that a blob + (represented by a commitment) evaluates to a given value at a given point. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + data = evm.message.data + if len(data) != 192: + raise KZGProofError + + versioned_hash = data[:32] + z = Bytes32(data[32:64]) + y = Bytes32(data[64:96]) + commitment = KZGCommitment(data[96:144]) + proof = Bytes48(data[144:192]) + + # GAS + charge_gas(evm, GAS_POINT_EVALUATION) + if kzg_commitment_to_versioned_hash(commitment) != versioned_hash: + raise KZGProofError + + # Verify KZG proof with z and y in big endian format + try: + kzg_proof_verification = verify_kzg_proof(commitment, z, y, proof) + except Exception as e: + raise KZGProofError from e + + if not kzg_proof_verification: + raise KZGProofError + + # Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded + # 32 byte big endian values + evm.output = Bytes( + U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + + U256(BLS_MODULUS).to_be_bytes32() + ) diff --git a/src/ethereum/prague/vm/precompiled_contracts/ripemd160.py b/src/ethereum/prague/vm/precompiled_contracts/ripemd160.py new file mode 100644 index 0000000000..6af1086a82 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/ripemd160.py @@ -0,0 +1,43 @@ +""" +Ethereum Virtual Machine (EVM) RIPEMD160 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `RIPEMD160` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.byte import left_pad_zero_bytes +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_RIPEMD160, GAS_RIPEMD160_WORD, charge_gas + + +def ripemd160(evm: Evm) -> None: + """ + Writes the ripemd160 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_RIPEMD160 + GAS_RIPEMD160_WORD * word_count) + + # OPERATION + hash_bytes = hashlib.new("ripemd160", data).digest() + padded_hash = left_pad_zero_bytes(hash_bytes, 32) + evm.output = padded_hash diff --git a/src/ethereum/prague/vm/precompiled_contracts/sha256.py b/src/ethereum/prague/vm/precompiled_contracts/sha256.py new file mode 100644 index 0000000000..db33a37967 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/sha256.py @@ -0,0 +1,40 @@ +""" +Ethereum Virtual Machine (EVM) SHA256 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `SHA256` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_SHA256, GAS_SHA256_WORD, charge_gas + + +def sha256(evm: Evm) -> None: + """ + Writes the sha256 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_SHA256 + GAS_SHA256_WORD * word_count) + + # OPERATION + evm.output = hashlib.sha256(data).digest() diff --git a/src/ethereum/prague/vm/runtime.py b/src/ethereum/prague/vm/runtime.py new file mode 100644 index 0000000000..d6bf90a812 --- /dev/null +++ b/src/ethereum/prague/vm/runtime.py @@ -0,0 +1,67 @@ +""" +Ethereum Virtual Machine (EVM) Runtime Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Runtime related operations used while executing EVM code. +""" +from typing import Set + +from ethereum_types.numeric import Uint, ulen + +from .instructions import Ops + + +def get_valid_jump_destinations(code: bytes) -> Set[Uint]: + """ + Analyze the evm code to obtain the set of valid jump destinations. + + Valid jump destinations are defined as follows: + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + + Note - Jump destinations are 0-indexed. + + Parameters + ---------- + code : + The EVM code which is to be executed. + + Returns + ------- + valid_jump_destinations: `Set[Uint]` + The set of valid jump destinations in the code. + """ + valid_jump_destinations = set() + pc = Uint(0) + + while pc < ulen(code): + try: + current_opcode = Ops(code[pc]) + except ValueError: + # Skip invalid opcodes, as they don't affect the jumpdest + # analysis. Nevertheless, such invalid opcodes would be caught + # and raised when the interpreter runs. + pc += Uint(1) + continue + + if current_opcode == Ops.JUMPDEST: + valid_jump_destinations.add(pc) + elif Ops.PUSH1.value <= current_opcode.value <= Ops.PUSH32.value: + # If PUSH-N opcodes are encountered, skip the current opcode along + # with the trailing data segment corresponding to the PUSH-N + # opcodes. + push_data_size = current_opcode.value - Ops.PUSH1.value + 1 + pc += Uint(push_data_size) + + pc += Uint(1) + + return valid_jump_destinations diff --git a/src/ethereum/prague/vm/stack.py b/src/ethereum/prague/vm/stack.py new file mode 100644 index 0000000000..f28a5b3b88 --- /dev/null +++ b/src/ethereum/prague/vm/stack.py @@ -0,0 +1,59 @@ +""" +Ethereum Virtual Machine (EVM) Stack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the stack operators for the EVM. +""" + +from typing import List + +from ethereum_types.numeric import U256 + +from .exceptions import StackOverflowError, StackUnderflowError + + +def pop(stack: List[U256]) -> U256: + """ + Pops the top item off of `stack`. + + Parameters + ---------- + stack : + EVM stack. + + Returns + ------- + value : `U256` + The top element on the stack. + + """ + if len(stack) == 0: + raise StackUnderflowError + + return stack.pop() + + +def push(stack: List[U256], value: U256) -> None: + """ + Pushes `value` onto `stack`. + + Parameters + ---------- + stack : + EVM stack. + + value : + Item to be pushed onto `stack`. + + """ + if len(stack) == 1024: + raise StackOverflowError + + return stack.append(value) diff --git a/tests/prague/__init__.py b/tests/prague/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/prague/test_evm_tools.py b/tests/prague/test_evm_tools.py new file mode 100644 index 0000000000..781f77bb2b --- /dev/null +++ b/tests/prague/test_evm_tools.py @@ -0,0 +1,42 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import TEST_FIXTURES +from tests.helpers.load_evm_tools_tests import ( + fetch_evm_tools_tests, + idfn, + load_evm_tools_test, +) + +ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] +TEST_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +FORK_NAME = "Prague" + +run_evm_tools_test = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + +SLOW_TESTS = ( + "CALLBlake2f_MaxRounds", + "CALLCODEBlake2f", + "CALLBlake2f", + "loopExp", + "loopMul", +) + + +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_evm_tools_tests( + TEST_DIR, + FORK_NAME, + SLOW_TESTS, + ), + ids=idfn, +) +def test_evm_tools(test_case: Dict) -> None: + run_evm_tools_test(test_case) diff --git a/tests/prague/test_rlp.py b/tests/prague/test_rlp.py new file mode 100644 index 0000000000..b6f610ab2f --- /dev/null +++ b/tests/prague/test_rlp.py @@ -0,0 +1,165 @@ +import pytest +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.prague.blocks import Block, Header, Log, Receipt, Withdrawal +from ethereum.prague.transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, + decode_transaction, + encode_transaction, +) +from ethereum.prague.utils.hexadecimal import hex_to_address +from ethereum.crypto.hash import keccak256 +from ethereum.utils.hexadecimal import hex_to_bytes256 + +hash1 = keccak256(b"foo") +hash2 = keccak256(b"bar") +hash3 = keccak256(b"baz") +hash4 = keccak256(b"foobar") +hash5 = keccak256(b"quux") +hash6 = keccak256(b"foobarbaz") + +address1 = hex_to_address("0x00000000219ab540356cbb839cbe05303d7705fa") +address2 = hex_to_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") +address3 = hex_to_address("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8") + +bloom = hex_to_bytes256( + "0x886480c00200620d84180d0470000c503081160044d05015808" + "0037401107060120040105281100100104500414203040a208003" + "4814200610da1208a638d16e440c024880800301e1004c2b02285" + "0602000084c3249a0c084569c90c2002001586241041e8004035a" + "4400a0100938001e041180083180b0340661372060401428c0200" + "87410402b9484028100049481900c08034864314688d001548c30" + "00828e542284180280006402a28a0264da00ac223004006209609" + "83206603200084040122a4739080501251542082020a4087c0002" + "81c08800898d0900024047380000127038098e090801080000429" + "0c84201661040200201c0004b8490ad588804" +) + +legacy_transaction = LegacyTransaction( + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"foo"), + U256(27), + U256(5), + U256(6), +) + +access_list_transaction = AccessListTransaction( + U64(1), + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ((address1, (hash1, hash2)), (address2, tuple())), + U256(27), + U256(5), + U256(6), +) + +transaction_1559 = FeeMarketTransaction( + U64(1), + U256(1), + Uint(7), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ((address1, (hash1, hash2)), (address2, tuple())), + U256(27), + U256(5), + U256(6), +) + +withdrawal = Withdrawal(U64(0), U64(1), address1, U256(2)) + + +header = Header( + parent_hash=hash1, + ommers_hash=hash2, + coinbase=address1, + state_root=hash3, + transactions_root=hash4, + receipt_root=hash5, + bloom=bloom, + difficulty=Uint(1), + number=Uint(2), + gas_limit=Uint(3), + gas_used=Uint(4), + timestamp=U256(5), + extra_data=Bytes(b"foobar"), + prev_randao=Bytes32(b"1234567890abcdef1234567890abcdef"), + nonce=Bytes8(b"12345678"), + base_fee_per_gas=Uint(6), + withdrawals_root=hash6, + parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), + blob_gas_used=U64(7), + excess_blob_gas=U64(8), +) + +block = Block( + header=header, + transactions=( + encode_transaction(legacy_transaction), + encode_transaction(access_list_transaction), + encode_transaction(transaction_1559), + ), + ommers=(), + withdrawals=(withdrawal,), +) + +log1 = Log( + address=address1, + topics=(hash1, hash2), + data=Bytes(b"foobar"), +) + +log2 = Log( + address=address1, + topics=(hash1,), + data=Bytes(b"quux"), +) + +receipt = Receipt( + succeeded=True, + cumulative_gas_used=Uint(1), + bloom=bloom, + logs=(log1, log2), +) + + +@pytest.mark.parametrize( + "rlp_object", + [ + legacy_transaction, + access_list_transaction, + transaction_1559, + header, + block, + log1, + log2, + receipt, + withdrawal, + ], +) +def test_cancun_rlp(rlp_object: rlp.Extended) -> None: + encoded = rlp.encode(rlp_object) + assert rlp.decode_to(type(rlp_object), encoded) == rlp_object + + +@pytest.mark.parametrize( + "tx", [legacy_transaction, access_list_transaction, transaction_1559] +) +def test_transaction_encoding(tx: Transaction) -> None: + encoded = encode_transaction(tx) + assert decode_transaction(encoded) == tx diff --git a/tests/prague/test_state_transition.py b/tests/prague/test_state_transition.py new file mode 100644 index 0000000000..60e34a24ed --- /dev/null +++ b/tests/prague/test_state_transition.py @@ -0,0 +1,105 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import TEST_FIXTURES +from tests.helpers.load_state_tests import ( + Load, + fetch_state_test_files, + idfn, + run_blockchain_st_test, +) + +fetch_cancun_tests = partial(fetch_state_test_files, network="Prague") + +FIXTURES_LOADER = Load("Prague", "prague") + +run_cancun_blockchain_st_tests = partial( + run_blockchain_st_test, load=FIXTURES_LOADER +) + +ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] +ETHEREUM_SPEC_TESTS_PATH = TEST_FIXTURES["execution_spec_tests"][ + "fixture_path" +] + + +# Run state tests +test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" + +SLOW_TESTS = ( + # GeneralStateTests + "stTimeConsuming/CALLBlake2f_MaxRounds.json", + "stTimeConsuming/static_Call50000_sha256.json", + "vmPerformance/loopExp.json", + "vmPerformance/loopMul.json", + "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Cancun", + "CALLBlake2f_d9g0v0_Cancun", + "CALLCODEBlake2f_d9g0v0", + # GeneralStateTests + "stRandom/randomStatetest177.json", + "stCreateTest/CreateOOGafterMaxCodesize.json", + # ValidBlockTest + "bcExploitTest/DelegateCallSpam.json", + # InvalidBlockTest + "bcUncleHeaderValidity/nonceWrong.json", + "bcUncleHeaderValidity/wrongMixHash.json", +) + +# These are tests that are considered to be incorrect, +# Please provide an explanation when adding entries +IGNORE_TESTS = ( + # ValidBlockTest + "bcForkStressTest/ForkStressTest.json", + "bcGasPricerTest/RPC_API_Test.json", + "bcMultiChainTest", + "bcTotalDifficultyTest", + # InvalidBlockTest + "bcForgedTest", + "bcMultiChainTest", + "GasLimitHigherThan2p63m1_Cancun", +) + +# All tests that recursively create a large number of frames (50000) +BIG_MEMORY_TESTS = ( + # GeneralStateTests + "50000_", + "/stQuadraticComplexityTest/", + "/stRandom2/", + "/stRandom/", + "/stSpecialTest/", + "stTimeConsuming/", + "stBadOpcode/", + "stStaticCall/", +) + +fetch_state_tests = partial( + fetch_cancun_tests, + test_dir, + ignore_list=IGNORE_TESTS, + slow_list=SLOW_TESTS, + big_memory_list=BIG_MEMORY_TESTS, +) + + +@pytest.mark.parametrize( + "test_case", + fetch_state_tests(), + ids=idfn, +) +def test_general_state_tests(test_case: Dict) -> None: + run_cancun_blockchain_st_tests(test_case) + + +# Run execution-spec-generated-tests +test_dir = f"{ETHEREUM_SPEC_TESTS_PATH}/fixtures/withdrawals" + + +@pytest.mark.parametrize( + "test_case", + fetch_cancun_tests(test_dir), + ids=idfn, +) +def test_execution_specs_generated_tests(test_case: Dict) -> None: + run_cancun_blockchain_st_tests(test_case) diff --git a/tests/prague/test_trie.py b/tests/prague/test_trie.py new file mode 100644 index 0000000000..1938c238a2 --- /dev/null +++ b/tests/prague/test_trie.py @@ -0,0 +1,89 @@ +import json +from typing import Any + +from ethereum.prague.fork_types import Bytes +from ethereum.prague.trie import Trie, root, trie_set +from ethereum.utils.hexadecimal import ( + has_hex_prefix, + hex_to_bytes, + remove_hex_prefix, +) +from tests.helpers import TEST_FIXTURES + +FIXTURE_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] + + +def to_bytes(data: str) -> Bytes: + if data is None: + return b"" + if has_hex_prefix(data): + return hex_to_bytes(data) + + return data.encode() + + +def test_trie_secure_hex() -> None: + tests = load_tests("hex_encoded_securetrie_test.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure() -> None: + tests = load_tests("trietest_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure_any_order() -> None: + tests = load_tests("trieanyorder_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie() -> None: + tests = load_tests("trietest.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_any_order() -> None: + tests = load_tests("trieanyorder.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def load_tests(path: str) -> Any: + with open(f"{FIXTURE_PATH}/TrieTests/" + path) as f: + tests = json.load(f) + + return tests From 9ca93fd6c3d9207e5541f145a92be17f2a497e89 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Tue, 23 Apr 2024 11:01:01 +0200 Subject: [PATCH 02/70] changes for prague --- setup.cfg | 5 +++++ src/ethereum/prague/__init__.py | 9 +++------ src/ethereum/prague/trie.py | 2 +- tests/prague/test_rlp.py | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6e006a2165..af82d6f7a0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,6 +104,11 @@ packages = ethereum/cancun/vm ethereum/cancun/vm/instructions ethereum/cancun/vm/precompiled_contracts + ethereum/prague + ethereum/prague/utils + ethereum/prague/vm + ethereum/prague/vm/instructions + ethereum/prague/vm/precompiled_contracts package_dir = diff --git a/src/ethereum/prague/__init__.py b/src/ethereum/prague/__init__.py index 04774a7578..09a10407a3 100644 --- a/src/ethereum/prague/__init__.py +++ b/src/ethereum/prague/__init__.py @@ -1,10 +1,7 @@ """ -The Prague fork introduces transient storage, exposes beacon chain roots, -introduces a new blob-carrying transaction type, adds a memory copying -instruction, limits self-destruct to only work for contracts created in the -same transaction, and adds an instruction to read the blob base fee. +The Prague fork. """ -from ethereum.fork_criteria import ByTimestamp +from ethereum.fork_criteria import Unscheduled -FORK_CRITERIA = ByTimestamp(1710338135) +FORK_CRITERIA = Unscheduled() diff --git a/src/ethereum/prague/trie.py b/src/ethereum/prague/trie.py index 082cbb5495..03ae159be7 100644 --- a/src/ethereum/prague/trie.py +++ b/src/ethereum/prague/trie.py @@ -33,8 +33,8 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from ethereum.cancun import trie as previous_trie from ethereum.crypto.hash import keccak256 -from ethereum.shanghai import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes from .blocks import Receipt, Withdrawal diff --git a/tests/prague/test_rlp.py b/tests/prague/test_rlp.py index b6f610ab2f..3a76f83b3d 100644 --- a/tests/prague/test_rlp.py +++ b/tests/prague/test_rlp.py @@ -3,6 +3,7 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.hash import keccak256 from ethereum.prague.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.prague.transactions import ( AccessListTransaction, @@ -13,7 +14,6 @@ encode_transaction, ) from ethereum.prague.utils.hexadecimal import hex_to_address -from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes256 hash1 = keccak256(b"foo") From 4916e4a89482f42b8548d4f69ed3c8b9dd2b0a7d Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 11:38:39 +0100 Subject: [PATCH 03/70] Implement EIP-7702 --- src/ethereum/prague/blocks.py | 37 ++- src/ethereum/prague/fork.py | 47 ++-- src/ethereum/prague/fork_types.py | 17 +- src/ethereum/prague/transactions.py | 87 ++++++- src/ethereum/prague/utils/message.py | 22 +- src/ethereum/prague/vm/__init__.py | 3 +- src/ethereum/prague/vm/eoa_delegation.py | 215 ++++++++++++++++++ .../prague/vm/instructions/environment.py | 35 ++- src/ethereum/prague/vm/instructions/system.py | 57 ++++- src/ethereum/prague/vm/interpreter.py | 20 +- src/ethereum/utils/hexadecimal.py | 19 +- .../evm_tools/loaders/fork_loader.py | 15 ++ .../evm_tools/loaders/transaction_loader.py | 37 ++- .../evm_tools/t8n/t8n_types.py | 3 + whitelist.txt | 2 + 15 files changed, 561 insertions(+), 55 deletions(-) create mode 100644 src/ethereum/prague/vm/eoa_delegation.py diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index 54ef88e15f..684b57aab7 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -11,13 +11,21 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + SetCodeTransaction, + Transaction, +) @slotted_freezable @@ -98,3 +106,30 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(receipt) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2, 3, 4) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 5bbb517ed6..f215e86955 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -23,9 +23,9 @@ from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal +from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root, VersionedHash +from .fork_types import Address, Authorization, Bloom, Root, VersionedHash from .state import ( State, TransientStorage, @@ -43,6 +43,7 @@ BlobTransaction, FeeMarketTransaction, LegacyTransaction, + SetCodeTransaction, Transaction, calculate_intrinsic_cost, decode_transaction, @@ -54,6 +55,7 @@ from .utils.hexadecimal import hex_to_address from .utils.message import prepare_message from .vm import Message +from .vm.eoa_delegation import is_valid_delegation from .vm.gas import ( calculate_blob_gas_price, calculate_data_fee, @@ -372,7 +374,9 @@ def check_transaction( sender_address = recover_sender(chain_id, tx) sender_account = get_account(state, sender_address) - if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): + if isinstance( + tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) + ): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock if tx.max_fee_per_gas < base_fee_per_gas: @@ -391,8 +395,6 @@ def check_transaction( max_gas_fee = tx.gas * tx.gas_price if isinstance(tx, BlobTransaction): - if not isinstance(tx.to, Address): - raise InvalidBlock if len(tx.blob_versioned_hashes) == 0: raise InvalidBlock for blob_versioned_hash in tx.blob_versioned_hashes: @@ -409,11 +411,22 @@ def check_transaction( blob_versioned_hashes = tx.blob_versioned_hashes else: blob_versioned_hashes = () + + if isinstance(tx, (BlobTransaction, SetCodeTransaction)): + if not isinstance(tx.to, Address): + raise InvalidBlock + + if isinstance(tx, SetCodeTransaction): + if not any(tx.authorizations): + raise InvalidBlock + if sender_account.nonce != tx.nonce: raise InvalidBlock if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): raise InvalidBlock - if sender_account.code != bytearray(): + if sender_account.code != bytearray() and not is_valid_delegation( + sender_account.code + ): raise InvalidSenderError("not EOA") return sender_address, effective_gas_price, blob_versioned_hashes @@ -452,14 +465,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - elif isinstance(tx, BlobTransaction): - return b"\x03" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -748,13 +754,23 @@ def process_transaction( preaccessed_storage_keys = set() preaccessed_addresses.add(env.coinbase) if isinstance( - tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), ): for address, keys in tx.access_list: preaccessed_addresses.add(address) for key in keys: preaccessed_storage_keys.add((address, key)) + authorizations: Tuple[Authorization, ...] = () + if isinstance(tx, SetCodeTransaction): + authorizations = tx.authorizations + message = prepare_message( sender, tx.to, @@ -764,6 +780,7 @@ def process_transaction( env, preaccessed_addresses=frozenset(preaccessed_addresses), preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + authorizations=authorizations, ) output = process_message_call(message, env) diff --git a/src/ethereum/prague/fork_types.py b/src/ethereum/prague/fork_types.py index dc287c84f1..26b6656190 100644 --- a/src/ethereum/prague/fork_types.py +++ b/src/ethereum/prague/fork_types.py @@ -17,7 +17,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes20, Bytes256 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U8, U64, U256, Uint from ..crypto.hash import Hash32, keccak256 @@ -62,3 +62,18 @@ def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: keccak256(raw_account_data.code), ) ) + + +@slotted_freezable +@dataclass +class Authorization: + """ + The authorization for a set code transaction. + """ + + chain_id: U256 + address: Address + nonce: U64 + y_parity: U8 + r: U256 + s: U256 diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index deb594c17c..153f08b655 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -9,14 +9,14 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError from .exceptions import TransactionTypeError -from .fork_types import Address, VersionedHash +from .fork_types import Address, Authorization, VersionedHash TX_BASE_COST = 21000 TX_DATA_COST_PER_NON_ZERO = 16 @@ -108,11 +108,34 @@ class BlobTransaction: s: U256 +@slotted_freezable +@dataclass +class SetCodeTransaction: + """ + The transaction type added in EIP-7702. + """ + + chain_id: U64 + nonce: U64 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + authorizations: Tuple[Authorization, ...] + y_parity: U256 + r: U256 + s: U256 + + Transaction = Union[ LegacyTransaction, AccessListTransaction, FeeMarketTransaction, BlobTransaction, + SetCodeTransaction, ] @@ -128,6 +151,8 @@ def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]: return b"\x02" + rlp.encode(tx) elif isinstance(tx, BlobTransaction): return b"\x03" + rlp.encode(tx) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(tx) else: raise Exception(f"Unable to encode transaction of type {type(tx)}") @@ -143,6 +168,8 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return rlp.decode_to(FeeMarketTransaction, tx[1:]) elif tx[0] == 3: return rlp.decode_to(BlobTransaction, tx[1:]) + elif tx[0] == 4: + return rlp.decode_to(SetCodeTransaction, tx[1:]) else: raise TransactionTypeError(tx[0]) else: @@ -178,7 +205,7 @@ def validate_transaction(tx: Transaction) -> bool: if calculate_intrinsic_cost(tx) > tx.gas: return False - if tx.nonce >= U256(U64.MAX_VALUE): + if U256(tx.nonce) >= U256(U64.MAX_VALUE): return False if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: return False @@ -209,6 +236,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: verified : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ + from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST from .vm.gas import init_code_cost data_cost = 0 @@ -220,17 +248,23 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: data_cost += TX_DATA_COST_PER_NON_ZERO if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance( - tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), ): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) @@ -291,6 +325,10 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, tx.y_parity, signing_hash_4844(tx) ) + elif isinstance(tx, SetCodeTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_7702(tx) + ) return Address(keccak256(public_key)[12:32]) @@ -451,3 +489,36 @@ def signing_hash_4844(tx: BlobTransaction) -> Hash32: ) ) ) + + +def signing_hash_7702(tx: SetCodeTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-7702 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x04" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.authorizations, + ) + ) + ) diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py index 7e91a7872c..3d1420b835 100644 --- a/src/ethereum/prague/utils/message.py +++ b/src/ethereum/prague/utils/message.py @@ -17,9 +17,10 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U256, Uint -from ..fork_types import Address +from ..fork_types import Address, Authorization from ..state import get_account from ..vm import Environment, Message +from ..vm.eoa_delegation import get_delegated_code_address from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address @@ -38,6 +39,7 @@ def prepare_message( preaccessed_storage_keys: FrozenSet[ Tuple[(Address, Bytes32)] ] = frozenset(), + authorizations: Tuple[Authorization, ...] = (), ) -> Message: """ Execute a transaction against the provided environment. @@ -70,12 +72,19 @@ def prepare_message( preaccessed_storage_keys: Storage keys that should be marked as accessed prior to the message call + authorizations: + Authorizations that should be applied to the message call. Returns ------- message: `ethereum.prague.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) + if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -87,16 +96,18 @@ def prepare_message( current_target = target msg_data = data code = get_account(env.state, target).code + + delegated_address = get_delegated_code_address(code) + if delegated_address is not None: + accessed_addresses.add(delegated_address) + code = get_account(env.state, delegated_address).code + if code_address is None: code_address = target else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, @@ -113,4 +124,5 @@ def prepare_message( accessed_addresses=accessed_addresses, accessed_storage_keys=set(preaccessed_storage_keys), parent_evm=None, + authorizations=authorizations, ) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 04bb7a353a..e73dba47bd 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -22,7 +22,7 @@ from ethereum.crypto.hash import Hash32 from ..blocks import Log -from ..fork_types import Address, VersionedHash +from ..fork_types import Address, Authorization, VersionedHash from ..state import State, TransientStorage, account_exists_and_is_empty from .precompiled_contracts import RIPEMD160_ADDRESS @@ -73,6 +73,7 @@ class Message: accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] parent_evm: Optional["Evm"] + authorizations: Tuple[Authorization, ...] @dataclass diff --git a/src/ethereum/prague/vm/eoa_delegation.py b/src/ethereum/prague/vm/eoa_delegation.py new file mode 100644 index 0000000000..b06cd144eb --- /dev/null +++ b/src/ethereum/prague/vm/eoa_delegation.py @@ -0,0 +1,215 @@ +""" +Set EOA account code. +""" + + +from typing import Optional, Tuple + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import keccak256 +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 Environment, Evm, Message + +SET_CODE_TX_MAGIC = b"\x05" +EOA_DELEGATION_MARKER = b"\xEF\x01\x00" +EOA_DELEGATION_SENTINEL = b"\xEF\x01" +EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) +EOA_DELEGATED_CODE_LENGTH = 23 +PER_EMPTY_ACCOUNT_COST = 25000 +PER_AUTH_BASE_COST = 12500 +NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") + + +def is_valid_delegation(code: bytes) -> bool: + """ + Whether the code is a valid delegation designation. + + Parameters + ---------- + code: `bytes` + The code to check. + + Returns + ------- + valid : `bool` + True if the code is a valid delegation designation, + False otherwise. + """ + if ( + len(code) == EOA_DELEGATED_CODE_LENGTH + and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER + ): + return True + return False + + +def get_delegated_code_address(code: bytes) -> Optional[Address]: + """ + Get the address to which the code delegates. + + Parameters + ---------- + code: `bytes` + The code to get the address from. + + Returns + ------- + address : `Optional[Address]` + The address of the delegated code. + """ + if is_valid_delegation(code): + return Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + return None + + +def recover_authority(authorization: Authorization) -> Address: + """ + Recover the authority address from the authorization. + + Parameters + ---------- + authorization + The authorization to recover the authority from. + + Raises + ------ + InvalidSignatureError + If the signature is invalid. + + Returns + ------- + authority : `Address` + The recovered authority address. + """ + y_parity, r, s = authorization.y_parity, authorization.r, authorization.s + if y_parity not in (0, 1): + raise InvalidSignatureError("Invalid y_parity in authorization") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("Invalid r value in authorization") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("Invalid s value in authorization") + + signing_hash = keccak256( + SET_CODE_TX_MAGIC + + rlp.encode( + ( + authorization.chain_id, + authorization.address, + authorization.nonce, + ) + ) + ) + + public_key = secp256k1_recover(r, s, U256(y_parity), signing_hash) + return Address(keccak256(public_key)[12:32]) + + +def access_delegation( + evm: Evm, address: Address +) -> Tuple[bool, Address, Bytes, Uint]: + """ + Get the delegation address, code, and the cost of access from the address. + + Parameters + ---------- + evm : `Evm` + The execution frame. + address : `Address` + The address to get the delegation from. + + Returns + ------- + delegation : `Tuple[bool, Address, Bytes, Uint]` + The delegation address, code, and access gas cost. + """ + code = get_account(evm.env.state, address).code + if not is_valid_delegation(code): + return False, address, code, Uint(0) + + address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code = get_account(evm.env.state, address).code + + return True, address, code, access_gas_cost + + +def set_delegation(message: Message, env: Environment) -> U256: + """ + Set the delegation code for the authorities in the message. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + refund_counter: `U256` + Refund from authority which already exists in state. + """ + refund_counter = U256(0) + for auth in message.authorizations: + if auth.chain_id not in (env.chain_id, U256(0)): + continue + + if auth.nonce >= U64.MAX_VALUE: + continue + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + continue + + message.accessed_addresses.add(authority) + + authority_account = get_account(env.state, authority) + authority_code = authority_account.code + + if authority_code != bytearray() and not is_valid_delegation( + authority_code + ): + continue + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + continue + + if account_exists(env.state, 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(env.state, authority, code_to_set) + + increment_nonce(env.state, authority) + + if message.code_address is None: + raise InvalidBlock("Invalid type 4 transaction: no target") + message.code = get_account(env.state, message.code_address).code + + if is_valid_delegation(message.code): + message.code_address = Address( + message.code[EOA_DELEGATION_MARKER_LENGTH:] + ) + message.accessed_addresses.add(message.code_address) + + message.code = get_account(env.state, message.code_address).code + + return refund_counter diff --git a/src/ethereum/prague/vm/instructions/environment.py b/src/ethereum/prague/vm/instructions/environment.py index 4ba384f9ba..08d6e9ae0d 100644 --- a/src/ethereum/prague/vm/instructions/environment.py +++ b/src/ethereum/prague/vm/instructions/environment.py @@ -21,6 +21,7 @@ from ...fork_types import EMPTY_ACCOUNT from ...state import get_account from ...utils.address import to_address +from ...vm.eoa_delegation import EOA_DELEGATION_SENTINEL, is_valid_delegation from ...vm.memory import buffer_read, memory_write from .. import Evm from ..exceptions import OutOfBoundsRead @@ -343,15 +344,19 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + if is_valid_delegation(code): + code = EOA_DELEGATION_SENTINEL + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -382,16 +387,19 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + if is_valid_delegation(code): + code = EOA_DELEGATION_SENTINEL + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -468,10 +476,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -479,7 +489,10 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + if is_valid_delegation(code): + code = EOA_DELEGATION_SENTINEL + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index da47df38cf..a75e036f06 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -32,6 +32,7 @@ compute_create2_contract_address, to_address, ) +from ...vm.eoa_delegation import access_delegation from .. import ( Evm, Message, @@ -128,6 +129,7 @@ def generic_create( accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, + authorizations=(), ) child_evm = process_create_message(child_message, evm.env) @@ -281,6 +283,8 @@ def generic_call( memory_input_size: U256, memory_output_start_position: U256, memory_output_size: U256, + code: bytes, + is_delegated_code: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -298,6 +302,12 @@ def generic_call( evm.memory, memory_input_start_position, memory_input_size ) code = get_account(evm.env.state, code_address).code + + if is_delegated_code and len(code) == 0: + evm.gas_left += gas + push(evm.stack, U256(1)) + return + child_message = Message( caller=caller, target=to, @@ -313,6 +323,7 @@ def generic_call( accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, + authorizations=(), ) child_evm = process_message(child_message, evm.env) @@ -366,6 +377,15 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -397,13 +417,15 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, + code, + is_delegated_code, ) # PROGRAM COUNTER @@ -445,6 +467,14 @@ def callcode(evm: Evm) -> None: evm.accessed_addresses.add(code_address) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE message_call_gas = calculate_message_call_gas( value, @@ -478,6 +508,8 @@ def callcode(evm: Evm) -> None: memory_input_size, memory_output_start_position, memory_output_size, + code, + is_delegated_code, ) # PROGRAM COUNTER @@ -573,6 +605,14 @@ def delegatecall(evm: Evm) -> None: evm.accessed_addresses.add(code_address) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost ) @@ -593,6 +633,8 @@ def delegatecall(evm: Evm) -> None: memory_input_size, memory_output_start_position, memory_output_size, + code, + is_delegated_code, ) # PROGRAM COUNTER @@ -631,6 +673,15 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -648,13 +699,15 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, memory_input_size, memory_output_start_position, memory_output_size, + code, + is_delegated_code, ) # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index 4a7c25a779..df02758171 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -14,7 +14,7 @@ from dataclasses import dataclass from typing import Iterable, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint, ulen from ethereum.trace import ( @@ -45,6 +45,7 @@ touch_account, ) from ..vm import Message +from ..vm.eoa_delegation import set_delegation from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from . import Environment, Evm @@ -77,6 +78,7 @@ class MessageCallOutput: 4. `accounts_to_delete`: Contracts which have self-destructed. 5. `touched_accounts`: Accounts that have been touched. 6. `error`: The error from the execution if any. + 7. `return_data`: The output of the execution. """ gas_left: Uint @@ -85,6 +87,7 @@ class MessageCallOutput: accounts_to_delete: Set[Address] touched_accounts: Iterable[Address] error: Optional[Exception] + return_data: Bytes def process_message_call( @@ -107,17 +110,26 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target ) or account_has_storage(env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), + U256(0), + tuple(), + set(), + set(), + AddressCollision(), + Bytes(b""), ) else: evm = process_create_message(message, env) else: + if message.authorizations != (): + refund_counter += set_delegation(message, env) evm = process_message(message, env) if account_exists_and_is_empty(env.state, Address(message.target)): evm.touched_accounts.add(Address(message.target)) @@ -126,12 +138,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error @@ -145,6 +156,7 @@ def process_message_call( accounts_to_delete=accounts_to_delete, touched_accounts=touched_accounts, error=evm.error, + return_data=evm.output, ) diff --git a/src/ethereum/utils/hexadecimal.py b/src/ethereum/utils/hexadecimal.py index 56c2452eab..d7a4f20ee5 100644 --- a/src/ethereum/utils/hexadecimal.py +++ b/src/ethereum/utils/hexadecimal.py @@ -12,7 +12,7 @@ Hexadecimal strings specific utility functions used in this specification. """ from ethereum_types.bytes import Bytes, Bytes8, Bytes20, Bytes32, Bytes256 -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U8, U64, U256, Uint from ethereum.crypto.hash import Hash32 @@ -176,6 +176,23 @@ def hex_to_uint(hex_string: str) -> Uint: return Uint(int(remove_hex_prefix(hex_string), 16)) +def hex_to_u8(hex_string: str) -> U8: + """ + Convert hex string to U8. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to U8. + + Returns + ------- + converted : `U8` + The U8 integer obtained from the given hexadecimal string. + """ + return U8(int(remove_hex_prefix(hex_string), 16)) + + def hex_to_u64(hex_string: str) -> U64: """ Convert hex string to U64. diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 5132577567..906775a61d 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -102,6 +102,11 @@ def signing_hash_1559(self) -> Any: """signing_hash_1559 function of the fork""" return self._module("transactions").signing_hash_1559 + @property + def signing_hash_7702(self) -> Any: + """signing_hash_7702 function of the fork""" + return self._module("transactions").signing_hash_7702 + @property def signing_hash_4844(self) -> Any: """signing_hash_4844 function of the fork""" @@ -167,6 +172,11 @@ def BlobTransaction(self) -> Any: """Blob transaction class of the fork""" return self._module("transactions").BlobTransaction + @property + def SetCodeTransaction(self) -> Any: + """Set code transaction class of the fork""" + return self._module("transactions").SetCodeTransaction + @property def Withdrawal(self) -> Any: """Withdrawal class of the fork""" @@ -282,6 +292,11 @@ def Message(self) -> Any: """Message class of the fork""" return self._module("vm").Message + @property + def Authorization(self) -> Any: + """Authorization class of the fork""" + return self._module("vm.eoa_delegation").Authorization + @property def TARGET_BLOB_GAS_PER_BLOCK(self) -> Any: """TARGET_BLOB_GAS_PER_BLOCK of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py index 76b0c25b8c..70c1e8d82a 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -14,9 +14,12 @@ hex_to_bytes, hex_to_bytes32, hex_to_hash, + hex_to_u8, + hex_to_u64, hex_to_u256, hex_to_uint, ) +from ethereum_spec_tools.evm_tools.utils import parse_hex_or_int class UnsupportedTx(Exception): @@ -85,6 +88,22 @@ def json_to_access_list(self) -> Any: ) return access_list + def json_to_authorizations(self) -> Any: + """Get the authorization list of the transaction.""" + authorizations = [] + for sublist in self.raw["authorizationList"]: + authorizations.append( + self.fork.Authorization( + chain_id=hex_to_u256(sublist.get("chainId")), + nonce=hex_to_u64(sublist.get("nonce")), + address=self.fork.hex_to_address(sublist.get("address")), + y_parity=hex_to_u8(sublist.get("v")), + r=hex_to_u256(sublist.get("r")), + s=hex_to_u256(sublist.get("s")), + ) + ) + return authorizations + def json_to_max_priority_fee_per_gas(self) -> Uint: """Get the max priority fee per gas of the transaction.""" return hex_to_uint(self.raw.get("maxPriorityFeePerGas")) @@ -145,23 +164,29 @@ def get_legacy_transaction(self) -> Any: def read(self) -> Any: """Convert json transaction data to a transaction object""" if "type" in self.raw: - tx_type = self.raw.get("type") - if tx_type == "0x3": + tx_type = parse_hex_or_int(self.raw.get("type"), Uint) + if tx_type == Uint(4): + tx_cls = self.fork.SetCodeTransaction + tx_byte_prefix = b"\x04" + elif tx_type == Uint(3): tx_cls = self.fork.BlobTransaction tx_byte_prefix = b"\x03" - elif tx_type == "0x2": + elif tx_type == Uint(2): tx_cls = self.fork.FeeMarketTransaction tx_byte_prefix = b"\x02" - elif tx_type == "0x1": + elif tx_type == Uint(1): tx_cls = self.fork.AccessListTransaction tx_byte_prefix = b"\x01" - elif tx_type == "0x0": + elif tx_type == Uint(0): tx_cls = self.get_legacy_transaction() tx_byte_prefix = b"" else: raise ValueError(f"Unknown transaction type: {tx_type}") else: - if "maxFeePerBlobGas" in self.raw: + if "authorizationList" in self.raw: + tx_cls = self.fork.SetCodeTransaction + tx_byte_prefix = b"\x04" + elif "maxFeePerBlobGas" in self.raw: tx_cls = self.fork.BlobTransaction tx_byte_prefix = b"\x03" elif "maxFeePerGas" in self.raw: diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 404f96bfe9..6c76210fac 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -277,6 +277,9 @@ def sign_transaction(self, json_tx: Any) -> None: elif isinstance(tx_decoded, t8n.fork.BlobTransaction): signing_hash = t8n.fork.signing_hash_4844(tx_decoded) v_addend = U256(0) + elif isinstance(tx_decoded, t8n.fork.SetCodeTransaction): + signing_hash = t8n.fork.signing_hash_7702(tx_decoded) + v_addend = U256(0) else: raise FatalException("Unknown transaction type") diff --git a/whitelist.txt b/whitelist.txt index de5d7ee9d7..6dfd7ae214 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -458,3 +458,5 @@ exponentiate monomial impl x2 + +eoa From ab1bf0335b97ec4cec014900d7818e10a7d64058 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 11:46:30 +0100 Subject: [PATCH 04/70] Implement EIP-2537 --- setup.cfg | 1 + src/ethereum/prague/vm/gas.py | 7 + .../vm/precompiled_contracts/__init__.py | 14 + .../bls12_381/__init__.py | 583 ++++++++++++++++++ .../bls12_381/bls12_381_g1.py | 148 +++++ .../bls12_381/bls12_381_g2.py | 148 +++++ .../bls12_381/bls12_381_pairing.py | 67 ++ .../vm/precompiled_contracts/mapping.py | 25 + 8 files changed, 993 insertions(+) create mode 100644 src/ethereum/prague/vm/precompiled_contracts/bls12_381/__init__.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g1.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g2.py create mode 100644 src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py diff --git a/setup.cfg b/setup.cfg index af82d6f7a0..0d98cf55f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -109,6 +109,7 @@ packages = ethereum/prague/vm ethereum/prague/vm/instructions ethereum/prague/vm/precompiled_contracts + ethereum/prague/vm/precompiled_contracts/bls12_381 package_dir = diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py index cc3ccc7c6b..32e35cdd0d 100644 --- a/src/ethereum/prague/vm/gas.py +++ b/src/ethereum/prague/vm/gas.py @@ -73,6 +73,13 @@ MIN_BLOB_GASPRICE = Uint(1) BLOB_GASPRICE_UPDATE_FRACTION = Uint(3338477) +GAS_BLS_G1_ADD = Uint(375) +GAS_BLS_G1_MUL = Uint(12000) +GAS_BLS_G1_MAP = Uint(5500) +GAS_BLS_G2_ADD = Uint(600) +GAS_BLS_G2_MUL = Uint(22500) +GAS_BLS_G2_MAP = Uint(23800) + @dataclass class ExtendMemory: diff --git a/src/ethereum/prague/vm/precompiled_contracts/__init__.py b/src/ethereum/prague/vm/precompiled_contracts/__init__.py index 7ec48ca69b..8ab92bb0f3 100644 --- a/src/ethereum/prague/vm/precompiled_contracts/__init__.py +++ b/src/ethereum/prague/vm/precompiled_contracts/__init__.py @@ -26,6 +26,13 @@ "ALT_BN128_PAIRING_CHECK_ADDRESS", "BLAKE2F_ADDRESS", "POINT_EVALUATION_ADDRESS", + "BLS12_G1_ADD_ADDRESS", + "BLS12_G1_MSM_ADDRESS", + "BLS12_G2_ADD_ADDRESS", + "BLS12_G2_MSM_ADDRESS", + "BLS12_PAIRING_ADDRESS", + "BLS12_MAP_FP_TO_G1_ADDRESS", + "BLS12_MAP_FP2_TO_G2_ADDRESS", ) ECRECOVER_ADDRESS = hex_to_address("0x01") @@ -38,3 +45,10 @@ ALT_BN128_PAIRING_CHECK_ADDRESS = hex_to_address("0x08") BLAKE2F_ADDRESS = hex_to_address("0x09") POINT_EVALUATION_ADDRESS = hex_to_address("0x0a") +BLS12_G1_ADD_ADDRESS = hex_to_address("0x0b") +BLS12_G1_MSM_ADDRESS = hex_to_address("0x0c") +BLS12_G2_ADD_ADDRESS = hex_to_address("0x0d") +BLS12_G2_MSM_ADDRESS = hex_to_address("0x0e") +BLS12_PAIRING_ADDRESS = hex_to_address("0x0f") +BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10") +BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11") diff --git a/src/ethereum/prague/vm/precompiled_contracts/bls12_381/__init__.py b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/__init__.py new file mode 100644 index 0000000000..2126a6ab39 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/__init__.py @@ -0,0 +1,583 @@ +""" +BLS12 381 Precompile +^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Precompile for BLS12-381 curve operations. +""" +from typing import Tuple, Union + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import ( + FQ, + FQ2, + b, + b2, + curve_order, + is_on_curve, + multiply, +) +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.typing import Point2D + +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter + +P = FQ.field_modulus + +G1_K_DISCOUNT = [ + 1000, + 949, + 848, + 797, + 764, + 750, + 738, + 728, + 719, + 712, + 705, + 698, + 692, + 687, + 682, + 677, + 673, + 669, + 665, + 661, + 658, + 654, + 651, + 648, + 645, + 642, + 640, + 637, + 635, + 632, + 630, + 627, + 625, + 623, + 621, + 619, + 617, + 615, + 613, + 611, + 609, + 608, + 606, + 604, + 603, + 601, + 599, + 598, + 596, + 595, + 593, + 592, + 591, + 589, + 588, + 586, + 585, + 584, + 582, + 581, + 580, + 579, + 577, + 576, + 575, + 574, + 573, + 572, + 570, + 569, + 568, + 567, + 566, + 565, + 564, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 551, + 550, + 549, + 548, + 547, + 547, + 546, + 545, + 544, + 543, + 542, + 541, + 540, + 540, + 539, + 538, + 537, + 536, + 536, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 529, + 528, + 528, + 527, + 526, + 525, + 525, + 524, + 523, + 522, + 522, + 521, + 520, + 520, + 519, +] + +G2_K_DISCOUNT = [ + 1000, + 1000, + 923, + 884, + 855, + 832, + 812, + 796, + 782, + 770, + 759, + 749, + 740, + 732, + 724, + 717, + 711, + 704, + 699, + 693, + 688, + 683, + 679, + 674, + 670, + 666, + 663, + 659, + 655, + 652, + 649, + 646, + 643, + 640, + 637, + 634, + 632, + 629, + 627, + 624, + 622, + 620, + 618, + 615, + 613, + 611, + 609, + 607, + 606, + 604, + 602, + 600, + 598, + 597, + 595, + 593, + 592, + 590, + 589, + 587, + 586, + 584, + 583, + 582, + 580, + 579, + 578, + 576, + 575, + 574, + 573, + 571, + 570, + 569, + 568, + 567, + 566, + 565, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 552, + 551, + 550, + 549, + 548, + 547, + 546, + 545, + 545, + 544, + 543, + 542, + 541, + 541, + 540, + 539, + 538, + 537, + 537, + 536, + 535, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 530, + 529, + 528, + 528, + 527, + 526, + 526, + 525, + 524, + 524, +] + +G1_MAX_DISCOUNT = 519 +G2_MAX_DISCOUNT = 524 +MULTIPLIER = Uint(1000) + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G1 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G1 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("Input should be 128 bytes long") + + x = int.from_bytes(data[:64], "big") + y = int.from_bytes(data[64:], "big") + + if x >= P: + raise InvalidParameter("Invalid field element") + if y >= P: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def G1_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G1 point to 128 bytes. + + Parameters + ---------- + point : + The G1 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 128 + + x, y = point + + x_bytes = int(x).to_bytes(64, "big") + y_bytes = int(y).to_bytes(64, "big") + + return x_bytes + y_bytes + + +def decode_G1_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 160 bytes to a G1 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G1 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 160: + InvalidParameter("Input should be 160 bytes long") + + p = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(128), U256(32)), "big") + + return p, m + + +def bytes_to_FQ( + data: Bytes, optimized: bool = False +) -> Union[FQ, OPTIMIZED_FQ]: + """ + Decode 64 bytes to a FQ element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ implementation. + + Returns + ------- + fq : Union[FQ, OPTIMIZED_FQ] + The FQ element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 64: + raise InvalidParameter("FQ should be 64 bytes long") + + c = int.from_bytes(data[:64], "big") + + if c >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ(c) + else: + return FQ(c) + + +def bytes_to_FQ2( + data: Bytes, optimized: bool = False +) -> Union[FQ2, OPTIMIZED_FQ2]: + """ + Decode 128 bytes to a FQ2 element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ2 implementation. + + Returns + ------- + fq2 : Union[FQ2, OPTIMIZED_FQ2] + The FQ2 element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 128: + raise InvalidParameter("FQ2 input should be 128 bytes long") + c_0 = int.from_bytes(data[:64], "big") + c_1 = int.from_bytes(data[64:], "big") + + if c_0 >= P: + raise InvalidParameter("Invalid field element") + if c_1 >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ2((c_0, c_1)) + else: + return FQ2((c_0, c_1)) + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 256 bytes to a G2 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G2 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 256: + raise InvalidParameter("G2 should be 256 bytes long") + + x = bytes_to_FQ2(data[:128]) + y = bytes_to_FQ2(data[128:]) + + assert isinstance(x, FQ2) and isinstance(y, FQ2) + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point + + +def FQ2_to_bytes(fq2: FQ2) -> Bytes: + """ + Encode a FQ2 point to 128 bytes. + + Parameters + ---------- + fq2 : + The FQ2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + c_0, c_1 = fq2.coeffs + return int(c_0).to_bytes(64, "big") + int(c_1).to_bytes(64, "big") + + +def G2_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G2 point to 256 bytes. + + Parameters + ---------- + point : + The G2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 256 + + x, y = point + + return FQ2_to_bytes(x) + FQ2_to_bytes(y) + + +def decode_G2_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 288 bytes to a G2 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G2 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 288: + InvalidParameter("Input should be 288 bytes long") + + p = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(256), U256(32)), "big") + + return p, m diff --git a/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g1.py new file mode 100644 index 0000000000..541395de40 --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g1.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G1 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G1, map_to_curve_G1 +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G1_ADD, + GAS_BLS_G1_MAP, + GAS_BLS_G1_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G1_K_DISCOUNT, + G1_MAX_DISCOUNT, + MULTIPLIER, + G1_to_bytes, + bytes_to_FQ, + bytes_to_G1, + decode_G1_scalar_pair, +) + +LENGTH_PER_PAIR = 160 + + +def bls12_g1_add(evm: Evm) -> None: + """ + The bls12_381 G1 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 256: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_ADD)) + + # OPERATION + p1 = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + p2 = bytes_to_G1(buffer_read(data, U256(128), U256(128))) + + result = add(p1, p2) + + evm.output = G1_to_bytes(result) + + +def bls12_g1_msm(evm: Evm) -> None: + """ + The bls12_381 G1 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G1_K_DISCOUNT[k - 1]) + else: + discount = Uint(G1_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G1_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G1_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G1_to_bytes(result) + + +def bls12_map_fp_to_g1(evm: Evm) -> None: + """ + Precompile to map field element to G1. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 64: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_MAP)) + + # OPERATION + field_element = bytes_to_FQ(data, True) + assert isinstance(field_element, OPTIMIZED_FQ) + + g1_uncompressed = clear_cofactor_G1(map_to_curve_G1(field_element)) + g1_normalised = normalize(g1_uncompressed) + + evm.output = G1_to_bytes(g1_normalised) diff --git a/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g2.py new file mode 100644 index 0000000000..bda7f3641f --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_g2.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 G2 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G2 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G2, map_to_curve_G2 +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G2_ADD, + GAS_BLS_G2_MAP, + GAS_BLS_G2_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G2_K_DISCOUNT, + G2_MAX_DISCOUNT, + MULTIPLIER, + G2_to_bytes, + bytes_to_FQ2, + bytes_to_G2, + decode_G2_scalar_pair, +) + +LENGTH_PER_PAIR = 288 + + +def bls12_g2_add(evm: Evm) -> None: + """ + The bls12_381 G2 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 512: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_ADD)) + + # OPERATION + p1 = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + p2 = bytes_to_G2(buffer_read(data, U256(256), U256(256))) + + result = add(p1, p2) + + evm.output = G2_to_bytes(result) + + +def bls12_g2_msm(evm: Evm) -> None: + """ + The bls12_381 G2 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G2_K_DISCOUNT[k - 1]) + else: + discount = Uint(G2_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G2_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G2_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G2_to_bytes(result) + + +def bls12_map_fp2_to_g2(evm: Evm) -> None: + """ + Precompile to map field element to G2. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 128: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_MAP)) + + # OPERATION + field_element = bytes_to_FQ2(data, True) + assert isinstance(field_element, OPTIMIZED_FQ2) + + g2_uncompressed = clear_cofactor_G2(map_to_curve_G2(field_element)) + g2_normalised = normalize(g2_uncompressed) + + evm.output = G2_to_bytes(g2_normalised) diff --git a/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py new file mode 100644 index 0000000000..2a03a6897a --- /dev/null +++ b/src/ethereum/prague/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py @@ -0,0 +1,67 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 PAIRING PRE-COMPILE +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the BLS12 381 pairing pre-compile. +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import FQ12, curve_order, multiply +from py_ecc.bls12_381.bls12_381_pairing import pairing + +from ....vm import Evm +from ....vm.gas import charge_gas +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import bytes_to_G1, bytes_to_G2 + + +def bls12_pairing(evm: Evm) -> None: + """ + The bls12_381 pairing precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid or if sub-group check fails. + """ + data = evm.message.data + if len(data) == 0 or len(data) % 384 != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // 384 + gas_cost = Uint(32600 * k + 37700) + charge_gas(evm, gas_cost) + + # OPERATION + result = FQ12.one() + for i in range(k): + g1_start = Uint(384 * i) + g2_start = Uint(384 * i + 128) + + g1_point = bytes_to_G1(buffer_read(data, U256(g1_start), U256(128))) + if multiply(g1_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + g2_point = bytes_to_G2(buffer_read(data, U256(g2_start), U256(256))) + if multiply(g2_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + result *= pairing(g2_point, g1_point) + + if result == FQ12.one(): + evm.output = b"\x00" * 31 + b"\x01" + else: + evm.output = b"\x00" * 32 diff --git a/src/ethereum/prague/vm/precompiled_contracts/mapping.py b/src/ethereum/prague/vm/precompiled_contracts/mapping.py index 7bd3416b6b..3094d8dff2 100644 --- a/src/ethereum/prague/vm/precompiled_contracts/mapping.py +++ b/src/ethereum/prague/vm/precompiled_contracts/mapping.py @@ -19,6 +19,13 @@ ALT_BN128_MUL_ADDRESS, ALT_BN128_PAIRING_CHECK_ADDRESS, BLAKE2F_ADDRESS, + BLS12_G1_ADD_ADDRESS, + BLS12_G1_MSM_ADDRESS, + BLS12_G2_ADD_ADDRESS, + BLS12_G2_MSM_ADDRESS, + BLS12_MAP_FP2_TO_G2_ADDRESS, + BLS12_MAP_FP_TO_G1_ADDRESS, + BLS12_PAIRING_ADDRESS, ECRECOVER_ADDRESS, IDENTITY_ADDRESS, MODEXP_ADDRESS, @@ -28,6 +35,17 @@ ) from .alt_bn128 import alt_bn128_add, alt_bn128_mul, alt_bn128_pairing_check from .blake2f import blake2f +from .bls12_381.bls12_381_g1 import ( + bls12_g1_add, + bls12_g1_msm, + bls12_map_fp_to_g1, +) +from .bls12_381.bls12_381_g2 import ( + bls12_g2_add, + bls12_g2_msm, + bls12_map_fp2_to_g2, +) +from .bls12_381.bls12_381_pairing import bls12_pairing from .ecrecover import ecrecover from .identity import identity from .modexp import modexp @@ -46,4 +64,11 @@ ALT_BN128_PAIRING_CHECK_ADDRESS: alt_bn128_pairing_check, BLAKE2F_ADDRESS: blake2f, POINT_EVALUATION_ADDRESS: point_evaluation, + BLS12_G1_ADD_ADDRESS: bls12_g1_add, + BLS12_G1_MSM_ADDRESS: bls12_g1_msm, + BLS12_G2_ADD_ADDRESS: bls12_g2_add, + BLS12_G2_MSM_ADDRESS: bls12_g2_msm, + BLS12_PAIRING_ADDRESS: bls12_pairing, + BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1, + BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2, } From 647fcc62aecd6468b83296cc781bdbc0938039f1 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:14:18 +0100 Subject: [PATCH 05/70] Implement EIP-6110 --- src/ethereum/genesis.py | 6 ++ src/ethereum/prague/blocks.py | 1 + src/ethereum/prague/fork.py | 84 +++++++++++++++++++ src/ethereum/prague/requests.py | 72 ++++++++++++++++ .../evm_tools/loaders/fixture_loader.py | 4 + .../evm_tools/loaders/fork_loader.py | 15 ++++ .../evm_tools/t8n/__init__.py | 55 ++++++++++-- src/ethereum_spec_tools/evm_tools/t8n/env.py | 1 + .../evm_tools/t8n/t8n_types.py | 14 +++- src/ethereum_spec_tools/evm_tools/utils.py | 13 +++ 10 files changed, 254 insertions(+), 11 deletions(-) create mode 100644 src/ethereum/prague/requests.py diff --git a/src/ethereum/genesis.py b/src/ethereum/genesis.py index 330022bd40..152a37c58b 100644 --- a/src/ethereum/genesis.py +++ b/src/ethereum/genesis.py @@ -255,6 +255,9 @@ def add_genesis_block( if has_field(hardfork.Header, "parent_beacon_block_root"): fields["parent_beacon_block_root"] = Hash32(b"\0" * 32) + if has_field(hardfork.Header, "requests_hash"): + fields["requests_hash"] = Hash32(b"\0" * 32) + genesis_header = hardfork.Header(**fields) block_fields = { @@ -266,6 +269,9 @@ def add_genesis_block( if has_field(hardfork.Block, "withdrawals"): block_fields["withdrawals"] = () + if has_field(hardfork.Block, "requests"): + block_fields["requests"] = () + genesis_block = hardfork.Block(**block_fields) chain.blocks.append(genesis_block) diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index 684b57aab7..15208d4ad0 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -68,6 +68,7 @@ class Header: blob_gas_used: U64 excess_blob_gas: U64 parent_beacon_block_root: Root + requests_hash: Hash32 @slotted_freezable diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index f215e86955..8e6da32588 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -26,6 +26,11 @@ from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Authorization, Bloom, Root, VersionedHash +from .requests import ( + DEPOSIT_REQUEST_TYPE, + compute_requests_hash, + parse_deposit_requests_from_receipt, +) from .state import ( State, TransientStorage, @@ -212,6 +217,8 @@ def state_transition(chain: BlockChain, block: Block) -> None: raise InvalidBlock if apply_body_output.blob_gas_used != block.header.blob_gas_used: raise InvalidBlock + if apply_body_output.requests_hash != block.header.requests_hash: + raise InvalidBlock chain.blocks.append(block) if len(chain.blocks) > 255: @@ -490,6 +497,8 @@ class ApplyBodyOutput: Trie root of all the withdrawals in the block. blob_gas_used : `ethereum.base_types.Uint` Total blob gas used in the block. + requests_hash : `Bytes` + Hash of all the requests in the block. """ block_gas_used: Uint @@ -499,6 +508,7 @@ class ApplyBodyOutput: state_root: Root withdrawals_root: Root blob_gas_used: Uint + requests_hash: Bytes def apply_body( @@ -576,6 +586,7 @@ def apply_body( secured=False, default=None ) block_logs: Tuple[Log, ...] = () + deposit_requests: Bytes = b"" beacon_block_roots_contract_code = get_account( state, BEACON_ROOTS_ADDRESS @@ -673,6 +684,8 @@ def apply_body( receipt, ) + deposit_requests += parse_deposit_requests_from_receipt(receipt) + block_logs += logs blob_gas_used += calculate_total_blob_gas(tx) if blob_gas_used > MAX_BLOB_GAS_PER_BLOCK: @@ -689,6 +702,22 @@ def apply_body( if account_exists_and_is_empty(state, wd.address): destroy_account(state, wd.address) + requests_from_execution = process_general_purpose_requests( + deposit_requests, + state, + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + chain_id, + excess_blob_gas, + ) + + requests_hash = compute_requests_hash(requests_from_execution) + return ApplyBodyOutput( block_gas_used, root(transactions_trie), @@ -697,9 +726,64 @@ def apply_body( state_root(state), root(withdrawals_trie), blob_gas_used, + requests_hash, ) +def process_general_purpose_requests( + deposit_requests: Bytes, + state: State, + block_hashes: List[Hash32], + coinbase: Address, + block_number: Uint, + base_fee_per_gas: Uint, + block_gas_limit: Uint, + block_time: U256, + prev_randao: Bytes32, + chain_id: U64, + excess_blob_gas: U64, +) -> List[Bytes]: + """ + Process all the requests in the block. + + Parameters + ---------- + deposit_requests : + The deposit requests. + state : + Current state. + block_hashes : + List of hashes of the previous 256 blocks. + coinbase : + Address of the block's coinbase. + block_number : + Block number. + base_fee_per_gas : + Base fee per gas. + block_gas_limit : + Initial amount of gas available for execution in this block. + block_time : + Time the block was produced. + prev_randao : + The previous randao from the beacon chain. + chain_id : + ID of the executing chain. + excess_blob_gas : + Excess blob gas. + + Returns + ------- + requests_from_execution : `List[Bytes]` + The requests from the execution + """ + # Requests are to be in ascending order of request type + requests_from_execution: List[Bytes] = [] + if len(deposit_requests) > 0: + requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) + + return requests_from_execution + + def process_transaction( env: vm.Environment, tx: Transaction ) -> Tuple[Uint, Tuple[Log, ...], Optional[Exception]]: diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py new file mode 100644 index 0000000000..80d2c232d9 --- /dev/null +++ b/src/ethereum/prague/requests.py @@ -0,0 +1,72 @@ +""" +Requests were introduced in EIP-7685 as a a general purpose framework for +storing contract-triggered requests. It extends the execution header and +body with a single field each to store the request information. +This inherently exposes the requests to the consensus layer, which can +then process each one. + +[EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 +""" + +from hashlib import sha256 +from typing import List, Union + +from ethereum_types.bytes import Bytes + +from .blocks import Receipt, decode_receipt +from .utils.hexadecimal import hex_to_address + +DEPOSIT_CONTRACT_ADDRESS = hex_to_address( + "0x00000000219ab540356cbb839cbe05303d7705fa" +) +DEPOSIT_REQUEST_TYPE = b"\x00" + + +def extract_deposit_data(data: Bytes) -> Bytes: + """ + Extracts Deposit Request from the DepositContract.DepositEvent data. + """ + return ( + data[192:240] # public_key + + data[288:320] # withdrawal_credentials + + data[352:360] # amount + + data[416:512] # signature + + data[544:552] # index + ) + + +def parse_deposit_requests_from_receipt( + receipt: Union[Bytes, Receipt], +) -> Bytes: + """ + Parse deposit requests from a receipt. + """ + deposit_requests: Bytes = b"" + decoded_receipt = decode_receipt(receipt) + for log in decoded_receipt.logs: + if log.address == DEPOSIT_CONTRACT_ADDRESS: + request = extract_deposit_data(log.data) + deposit_requests += request + + return deposit_requests + + +def compute_requests_hash(requests: List[Bytes]) -> Bytes: + """ + Get the hash of the requests using the SHA2-256 algorithm. + + Parameters + ---------- + requests : Bytes + The requests to hash. + + Returns + ------- + requests_hash : Bytes + The hash of the requests. + """ + m = sha256() + for request in requests: + m.update(sha256(request).digest()) + + return m.digest() diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index db6c5ade55..621a69eda3 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -178,4 +178,8 @@ def json_to_header(self, raw: Any) -> Any: ) parameters.append(parent_beacon_block_root) + if "requestsHash" in raw: + requests_hash = hex_to_bytes32(raw.get("requestsHash")) + parameters.append(requests_hash) + return self.fork.Header(*parameters) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 906775a61d..0a7f1845b9 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -47,6 +47,11 @@ def is_after_fork(self, target_fork_name: str) -> bool: break return return_value + @property + def process_general_purpose_requests(self) -> Any: + """process_general_purpose_requests function of the given fork.""" + return self._module("fork").process_general_purpose_requests + @property def calculate_block_difficulty(self) -> Any: """calculate_block_difficulty function of the given fork.""" @@ -132,6 +137,16 @@ def Block(self) -> Any: """Block class of the fork""" return self._module("blocks").Block + @property + def parse_deposit_requests_from_receipt(self) -> Any: + """parse_deposit_requests_from_receipt function of the fork""" + return self._module("requests").parse_deposit_requests_from_receipt + + @property + def compute_requests_hash(self) -> Any: + """compute_requests_hash function of the fork""" + return self._module("requests").compute_requests_hash + @property def Bloom(self) -> Any: """Bloom class of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index a5c2be531f..63b7bbf1b5 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -9,6 +9,7 @@ from typing import Any, TextIO from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum import trace @@ -73,6 +74,8 @@ def t8n_arguments(subparsers: argparse._SubParsersAction) -> None: t8n_parser.add_argument("--trace.nostack", action="store_true") t8n_parser.add_argument("--trace.returndata", action="store_true") + t8n_parser.add_argument("--state-test", action="store_true") + class T8N(Load): """The class that carries out the transition""" @@ -124,15 +127,6 @@ def __init__( self.env.block_difficulty, self.env.base_fee_per_gas ) - if self.fork.is_after_fork("ethereum.cancun"): - self.SYSTEM_ADDRESS = self.fork.hex_to_address( - "0xfffffffffffffffffffffffffffffffffffffffe" - ) - self.BEACON_ROOTS_ADDRESS = self.fork.hex_to_address( - "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" - ) - self.SYSTEM_TRANSACTION_GAS = Uint(30000000) - @property def BLOCK_REWARD(self) -> Any: """ @@ -313,6 +307,12 @@ def apply_body(self) -> None: receipts_trie = self.fork.Trie(secured=False, default=None) block_logs = () blob_gas_used = Uint(0) + if ( + self.fork.is_after_fork("ethereum.prague") + and not self.options.state_test + ): + deposit_requests: Bytes = b"" + if ( self.fork.is_after_fork("ethereum.cancun") @@ -410,6 +410,13 @@ def apply_body(self) -> None: rlp.encode(Uint(i)), receipt, ) + if ( + self.fork.is_after_fork("ethereum.prague") + and not self.options.state_test + ): + deposit_requests += ( + self.fork.parse_deposit_requests_from_receipt(receipt) + ) self.txs.add_receipt(tx, gas_consumed) @@ -447,6 +454,29 @@ def apply_body(self) -> None: self.result.blob_gas_used = blob_gas_used self.result.excess_blob_gas = self.env.excess_blob_gas + if ( + self.fork.is_after_fork("ethereum.prague") + and not self.options.state_test + ): + requests_from_execution = ( + self.fork.process_general_purpose_requests( + deposit_requests, + self.alloc.state, + self.env.block_hashes, + self.env.coinbase, + self.env.block_number, + self.env.base_fee_per_gas, + self.env.block_gas_limit, + self.env.block_timestamp, + self.env.prev_randao, + self.chain_id, + self.env.excess_blob_gas, + ) + ) + requests_hash = self.fork.compute_requests_hash( + requests_from_execution + ) + self.result.state_root = self.fork.state_root(self.alloc.state) self.result.tx_root = self.fork.root(transactions_trie) self.result.receipt_root = self.fork.root(receipts_trie) @@ -456,6 +486,13 @@ def apply_body(self) -> None: self.result.receipts = self.txs.successful_receipts self.result.gas_used = block_gas_used + if ( + self.fork.is_after_fork("ethereum.prague") + and not self.options.state_test + ): + self.result.requests_hash = requests_hash + self.result.requests = requests_from_execution + def run(self) -> int: """Run the transition and provide the relevant outputs""" try: diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index 802c524304..373d9d0a8b 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -52,6 +52,7 @@ class Env: parent_excess_blob_gas: Optional[U64] parent_blob_gas_used: Optional[U64] excess_blob_gas: Optional[U64] + requests: Any def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): if t8n.options.input_env == "stdin": diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 6c76210fac..d5f0f67284 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -9,11 +9,11 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.hash import keccak256 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_u256, hex_to_uint from ..loaders.transaction_loader import TransactionLoad, UnsupportedTx -from ..utils import FatalException, secp256k1_sign +from ..utils import FatalException, encode_to_hex, secp256k1_sign if TYPE_CHECKING: from . import T8N @@ -309,6 +309,8 @@ class Result: gas_used: Any = None excess_blob_gas: Optional[U64] = None blob_gas_used: Optional[Uint] = None + requests_hash: Optional[Hash32] = None + requests: Optional[List[Bytes]] = None def to_json(self) -> Any: """Encode the result to JSON""" @@ -351,4 +353,12 @@ def to_json(self) -> Any: for item in self.receipts ] + if self.requests_hash is not None: + assert self.requests is not None + + data["requestsHash"] = encode_to_hex(self.requests_hash) + # T8N doesn't consider the request type byte to be part of the + # request + data["requests"] = [encode_to_hex(req) for req in self.requests] + return data diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/evm_tools/utils.py index 258f9d1966..fb4ea8870f 100644 --- a/src/ethereum_spec_tools/evm_tools/utils.py +++ b/src/ethereum_spec_tools/evm_tools/utils.py @@ -14,6 +14,7 @@ Sequence, Tuple, TypeVar, + Union, ) import coincurve @@ -183,3 +184,15 @@ def secp256k1_sign(msg_hash: Hash32, secret_key: int) -> Tuple[U256, ...]: U256.from_be_bytes(signature[32:64]), U256(signature[64]), ) + + +def encode_to_hex(data: Union[bytes, int]) -> str: + """ + Encode the data to a hex string. + """ + if isinstance(data, int): + return hex(data) + elif isinstance(data, bytes): + return "0x" + data.hex() + else: + raise Exception("Invalid data type") From 5f188de7054fbe2cbb95db0adeaae31794069b85 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:31:07 +0100 Subject: [PATCH 06/70] Implement EIP-7002 --- src/ethereum/prague/fork.py | 185 +++++++++++++----- src/ethereum/prague/requests.py | 1 + .../evm_tools/loaders/fork_loader.py | 25 +++ .../evm_tools/t8n/__init__.py | 59 ++---- 4 files changed, 180 insertions(+), 90 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 8e6da32588..752a6321c0 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -28,6 +28,7 @@ from .fork_types import Address, Authorization, Bloom, Root, VersionedHash from .requests import ( DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, compute_requests_hash, parse_deposit_requests_from_receipt, ) @@ -67,7 +68,7 @@ calculate_excess_blob_gas, calculate_total_blob_gas, ) -from .vm.interpreter import process_message_call +from .vm.interpreter import MessageCallOutput, process_message_call BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) ELASTICITY_MULTIPLIER = Uint(2) @@ -82,6 +83,10 @@ MAX_BLOB_GAS_PER_BLOCK = Uint(786432) VERSIONED_HASH_VERSION_KZG = b"\x01" +WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA" +) + @dataclass class BlockChain: @@ -511,6 +516,108 @@ class ApplyBodyOutput: requests_hash: Bytes +def process_system_transaction( + target_address: Address, + data: Bytes, + block_hashes: List[Hash32], + coinbase: Address, + block_number: Uint, + base_fee_per_gas: Uint, + block_gas_limit: Uint, + block_time: U256, + prev_randao: Bytes32, + state: State, + chain_id: U64, + excess_blob_gas: U64, +) -> MessageCallOutput: + """ + Process a system transaction. + + Parameters + ---------- + target_address : + Address of the contract to call. + data : + Data to pass to the contract. + block_hashes : + List of hashes of the previous 256 blocks. + coinbase : + Address of the block's coinbase. + block_number : + Block number. + base_fee_per_gas : + Base fee per gas. + block_gas_limit : + Gas limit of the block. + block_time : + Time the block was produced. + prev_randao : + Previous randao value. + state : + Current state. + chain_id : + ID of the chain. + excess_blob_gas : + Excess blob gas. + + Returns + ------- + system_tx_output : `MessageCallOutput` + Output of processing the system transaction. + """ + system_contract_code = get_account(state, target_address).code + + system_tx_message = Message( + caller=SYSTEM_ADDRESS, + target=target_address, + gas=SYSTEM_TRANSACTION_GAS, + value=U256(0), + data=data, + code=system_contract_code, + depth=Uint(0), + current_target=target_address, + code_address=target_address, + should_transfer_value=False, + is_static=False, + accessed_addresses=set(), + accessed_storage_keys=set(), + parent_evm=None, + authorizations=(), + ) + + system_tx_env = vm.Environment( + caller=SYSTEM_ADDRESS, + block_hashes=block_hashes, + origin=SYSTEM_ADDRESS, + coinbase=coinbase, + number=block_number, + gas_limit=block_gas_limit, + base_fee_per_gas=base_fee_per_gas, + gas_price=base_fee_per_gas, + time=block_time, + prev_randao=prev_randao, + state=state, + chain_id=chain_id, + traces=[], + excess_blob_gas=excess_blob_gas, + blob_versioned_hashes=(), + transient_storage=TransientStorage(), + ) + + system_tx_output = process_message_call(system_tx_message, system_tx_env) + + # TODO: Empty accounts in post-merge forks are impossible + # see Ethereum Improvement Proposal 7523. + # This line is only included to support invalid tests in the test suite + # and will have to be removed in the future. + # See https://github.com/ethereum/execution-specs/issues/955 + destroy_touched_empty_accounts( + system_tx_env.state, system_tx_output.touched_accounts + ) + + return system_tx_output + + def apply_body( state: State, block_hashes: List[Hash32], @@ -588,51 +695,21 @@ def apply_body( block_logs: Tuple[Log, ...] = () deposit_requests: Bytes = b"" - beacon_block_roots_contract_code = get_account( - state, BEACON_ROOTS_ADDRESS - ).code - - system_tx_message = Message( - caller=SYSTEM_ADDRESS, - target=BEACON_ROOTS_ADDRESS, - gas=SYSTEM_TRANSACTION_GAS, - value=U256(0), - data=parent_beacon_block_root, - code=beacon_block_roots_contract_code, - depth=Uint(0), - current_target=BEACON_ROOTS_ADDRESS, - code_address=BEACON_ROOTS_ADDRESS, - should_transfer_value=False, - is_static=False, - accessed_addresses=set(), - accessed_storage_keys=set(), - parent_evm=None, - ) - - system_tx_env = vm.Environment( - caller=SYSTEM_ADDRESS, - origin=SYSTEM_ADDRESS, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=base_fee_per_gas, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=(), - transient_storage=TransientStorage(), + process_system_transaction( + BEACON_ROOTS_ADDRESS, + parent_beacon_block_root, + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + state, + chain_id, + excess_blob_gas, ) - system_tx_output = process_message_call(system_tx_message, system_tx_env) - - destroy_touched_empty_accounts( - system_tx_env.state, system_tx_output.touched_accounts - ) for i, tx in enumerate(map(decode_transaction, transactions)): trie_set( @@ -781,6 +858,26 @@ def process_general_purpose_requests( if len(deposit_requests) > 0: requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) + system_withdrawal_tx_output = process_system_transaction( + WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + b"", + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + state, + chain_id, + excess_blob_gas, + ) + + if len(system_withdrawal_tx_output.return_data) > 0: + requests_from_execution.append( + WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data + ) + return requests_from_execution diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index 80d2c232d9..2516783ce2 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -20,6 +20,7 @@ "0x00000000219ab540356cbb839cbe05303d7705fa" ) DEPOSIT_REQUEST_TYPE = b"\x00" +WITHDRAWAL_REQUEST_TYPE = b"\x01" def extract_deposit_data(data: Bytes) -> Bytes: diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 0a7f1845b9..d1999ca2b1 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -47,11 +47,36 @@ def is_after_fork(self, target_fork_name: str) -> bool: break return return_value + @property + def SYSTEM_TRANSACTION_GAS(self) -> Any: + """SYSTEM_TRANSACTION_GAS of the given fork.""" + return self._module("fork").SYSTEM_TRANSACTION_GAS + + @property + def SYSTEM_ADDRESS(self) -> Any: + """SYSTEM_ADDRESS of the given fork.""" + return self._module("fork").SYSTEM_ADDRESS + + @property + def BEACON_ROOTS_ADDRESS(self) -> Any: + """BEACON_ROOTS_ADDRESS of the given fork.""" + return self._module("fork").BEACON_ROOTS_ADDRESS + + @property + def WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: + """WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" + return self._module("fork").WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS + @property def process_general_purpose_requests(self) -> Any: """process_general_purpose_requests function of the given fork.""" return self._module("fork").process_general_purpose_requests + @property + def process_system_transaction(self) -> Any: + """process_system_transaction function of the given fork.""" + return self._module("fork").process_system_transaction + @property def calculate_block_difficulty(self) -> Any: """calculate_block_difficulty function of the given fork.""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 63b7bbf1b5..e08db18aff 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -318,52 +318,19 @@ def apply_body(self) -> None: self.fork.is_after_fork("ethereum.cancun") and self.env.parent_beacon_block_root is not None ): - beacon_block_roots_contract_code = self.fork.get_account( - self.alloc.state, self.BEACON_ROOTS_ADDRESS - ).code - - system_tx_message = self.fork.Message( - caller=self.SYSTEM_ADDRESS, - target=self.BEACON_ROOTS_ADDRESS, - gas=self.SYSTEM_TRANSACTION_GAS, - value=U256(0), - data=self.env.parent_beacon_block_root, - code=beacon_block_roots_contract_code, - depth=Uint(0), - current_target=self.BEACON_ROOTS_ADDRESS, - code_address=self.BEACON_ROOTS_ADDRESS, - should_transfer_value=False, - is_static=False, - accessed_addresses=set(), - accessed_storage_keys=set(), - parent_evm=None, - ) - - system_tx_env = self.fork.Environment( - caller=self.SYSTEM_ADDRESS, - origin=self.SYSTEM_ADDRESS, - block_hashes=self.env.block_hashes, - coinbase=self.env.coinbase, - number=self.env.block_number, - gas_limit=self.env.block_gas_limit, - base_fee_per_gas=self.env.base_fee_per_gas, - gas_price=self.env.base_fee_per_gas, - time=self.env.block_timestamp, - prev_randao=self.env.prev_randao, - state=self.alloc.state, - chain_id=self.chain_id, - traces=[], - excess_blob_gas=self.env.excess_blob_gas, - blob_versioned_hashes=(), - transient_storage=self.fork.TransientStorage(), - ) - - system_tx_output = self.fork.process_message_call( - system_tx_message, system_tx_env - ) - - self.fork.destroy_touched_empty_accounts( - system_tx_env.state, system_tx_output.touched_accounts + self.fork.process_system_transaction( + self.fork.BEACON_ROOTS_ADDRESS, + self.env.parent_beacon_block_root, + self.env.block_hashes, + self.env.coinbase, + self.env.block_number, + self.env.base_fee_per_gas, + self.env.block_gas_limit, + self.env.block_timestamp, + self.env.prev_randao, + self.alloc.state, + self.chain_id, + self.env.excess_blob_gas, ) for i, (tx_idx, tx) in enumerate(self.txs.transactions): From 78828a7e7003230538194747600e2c5dedd2c2b7 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:38:02 +0100 Subject: [PATCH 07/70] Implement EIP-7251 --- src/ethereum/prague/fork.py | 25 +++++++++++++++++++ src/ethereum/prague/requests.py | 1 + .../evm_tools/loaders/fork_loader.py | 5 ++++ 3 files changed, 31 insertions(+) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 752a6321c0..4a534ad788 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -27,6 +27,7 @@ from .bloom import logs_bloom from .fork_types import Address, Authorization, Bloom, Root, VersionedHash from .requests import ( + CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, compute_requests_hash, @@ -86,6 +87,9 @@ WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( "0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA" ) +CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x00431F263cE400f4455c2dCf564e53007Ca4bbBb" +) @dataclass @@ -878,6 +882,27 @@ def process_general_purpose_requests( WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data ) + system_consolidation_tx_output = process_system_transaction( + CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + b"", + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + state, + chain_id, + excess_blob_gas, + ) + + if len(system_consolidation_tx_output.return_data) > 0: + requests_from_execution.append( + CONSOLIDATION_REQUEST_TYPE + + system_consolidation_tx_output.return_data + ) + return requests_from_execution diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index 2516783ce2..a72f8f35ae 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -21,6 +21,7 @@ ) DEPOSIT_REQUEST_TYPE = b"\x00" WITHDRAWAL_REQUEST_TYPE = b"\x01" +CONSOLIDATION_REQUEST_TYPE = b"\x02" def extract_deposit_data(data: Bytes) -> Bytes: diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index d1999ca2b1..c48444b02e 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -67,6 +67,11 @@ def WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: """WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" return self._module("fork").WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS + @property + def CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: + """CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" + return self._module("fork").CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS + @property def process_general_purpose_requests(self) -> Any: """process_general_purpose_requests function of the given fork.""" From 14ed7502510f9711e538a046a17d26d54c071198 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:44:14 +0100 Subject: [PATCH 08/70] Implement EIP-2935 --- src/ethereum/prague/fork.py | 18 ++++++++++++++++++ .../evm_tools/loaders/fork_loader.py | 10 ++++++++++ .../evm_tools/t8n/__init__.py | 14 ++++++++++++++ src/ethereum_spec_tools/evm_tools/t8n/env.py | 9 +++++++-- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 4a534ad788..ba66bccb33 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -90,6 +90,10 @@ CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( "0x00431F263cE400f4455c2dCf564e53007Ca4bbBb" ) +HISTORY_STORAGE_ADDRESS = hex_to_address( + "0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC" +) +HISTORY_SERVE_WINDOW = 8192 @dataclass @@ -714,6 +718,20 @@ def apply_body( excess_blob_gas, ) + process_system_transaction( + HISTORY_STORAGE_ADDRESS, + block_hashes[-1], # The parent hash + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + state, + chain_id, + excess_blob_gas, + ) for i, tx in enumerate(map(decode_transaction, transactions)): trie_set( diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index c48444b02e..2409e5a883 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -62,6 +62,11 @@ def BEACON_ROOTS_ADDRESS(self) -> Any: """BEACON_ROOTS_ADDRESS of the given fork.""" return self._module("fork").BEACON_ROOTS_ADDRESS + @property + def HISTORY_STORAGE_ADDRESS(self) -> Any: + """HISTORY_STORAGE_ADDRESS of the given fork.""" + return self._module("fork").HISTORY_STORAGE_ADDRESS + @property def WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: """WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" @@ -72,6 +77,11 @@ def CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: """CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" return self._module("fork").CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS + @property + def HISTORY_SERVE_WINDOW(self) -> Any: + """HISTORY_SERVE_WINDOW of the given fork.""" + return self._module("fork").HISTORY_SERVE_WINDOW + @property def process_general_purpose_requests(self) -> Any: """process_general_purpose_requests function of the given fork.""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index e08db18aff..1868d45717 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -313,6 +313,20 @@ def apply_body(self) -> None: ): deposit_requests: Bytes = b"" + self.fork.process_system_transaction( + self.fork.HISTORY_STORAGE_ADDRESS, + self.env.parent_hash, + self.env.block_hashes, + self.env.coinbase, + self.env.block_number, + self.env.base_fee_per_gas, + self.env.block_gas_limit, + self.env.block_timestamp, + self.env.prev_randao, + self.alloc.state, + self.chain_id, + self.env.excess_blob_gas, + ) if ( self.fork.is_after_fork("ethereum.cancun") diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index 373d9d0a8b..6d272788a7 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -36,6 +36,7 @@ class Env: block_gas_limit: Uint block_number: Uint block_timestamp: U256 + parent_hash: Any withdrawals: Any block_difficulty: Optional[Uint] prev_randao: Optional[Bytes32] @@ -70,7 +71,7 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): self.read_block_difficulty(data, t8n) self.read_base_fee_per_gas(data, t8n) self.read_randao(data, t8n) - self.read_block_hashes(data) + self.read_block_hashes(data, t8n) self.read_ommers(data, t8n) self.read_withdrawals(data, t8n) @@ -231,10 +232,14 @@ def read_block_difficulty(self, data: Any, t8n: "T8N") -> None: args.append(False) self.block_difficulty = t8n.fork.calculate_block_difficulty(*args) - def read_block_hashes(self, data: Any) -> None: + def read_block_hashes(self, data: Any, t8n: "T8N") -> None: """ Read the block hashes. Returns a maximum of 256 block hashes. """ + self.parent_hash = None + if t8n.fork.is_after_fork("ethereum.prague"): + self.parent_hash = Hash32(hex_to_bytes(data["parentHash"])) + # Read the block hashes block_hashes: List[Any] = [] # Store a maximum of 256 block hashes. From d9f70f92b14eb20f47ede75cae1d9134d7c31489 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:49:37 +0100 Subject: [PATCH 09/70] Implement EIP-7623 --- src/ethereum/prague/fork.py | 33 +++++++++++++++------ src/ethereum/prague/transactions.py | 46 ++++++++++++++++++++--------- whitelist.txt | 2 ++ 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index ba66bccb33..c673746cda 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -46,6 +46,8 @@ state_root, ) from .transactions import ( + FLOOR_CALLDATA_COST, + TX_BASE_COST, AccessListTransaction, BlobTransaction, FeeMarketTransaction, @@ -966,7 +968,9 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) + + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( @@ -1009,17 +1013,28 @@ def process_transaction( output = process_message_call(message, env) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + # For EIP-7623 we first calculate the execution_gas_used, which includes + # the execution gas refund. + execution_gas_used = tx.gas - output.gas_left + gas_refund = min( + execution_gas_used // Uint(5), Uint(output.refund_counter) + ) + execution_gas_used -= gas_refund + + # EIP-7623 floor price (note: no EVM costs) + floor_gas_cost = Uint( + tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST + ) + # Transactions with less execution_gas_used than the floor pay at the + # floor cost. + total_gas_used = max(execution_gas_used, floor_gas_cost) + + output.gas_left = tx.gas - total_gas_used + gas_refund_amount = output.gas_left * env.gas_price # For non-1559 transactions env.gas_price == tx.gas_price priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas - - total_gas_used = gas_used - gas_refund + transaction_fee = total_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index 153f08b655..00594c790c 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address, Authorization, VersionedHash -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +FLOOR_CALLDATA_COST = Uint(10) +STANDARD_CALLDATA_TOKEN_COST = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -203,7 +203,9 @@ def validate_transaction(tx: Transaction) -> bool: """ from .vm.interpreter import MAX_CODE_SIZE - if calculate_intrinsic_cost(tx) > tx.gas: + intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) + gas_floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) + if max(intrinsic_gas, gas_floor) > tx.gas: return False if U256(tx.nonce) >= U256(U64.MAX_VALUE): return False @@ -213,7 +215,7 @@ def validate_transaction(tx: Transaction) -> bool: return True -def calculate_intrinsic_cost(tx: Transaction) -> Uint: +def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: """ Calculates the gas that is charged before execution is started. @@ -235,17 +237,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: ------- verified : `ethereum.base_types.Uint` The intrinsic cost of the transaction. + tokens_in_calldata : `ethereum.base_types.Uint` + The eip-7623 calldata tokens used by the transaction. """ from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST from .vm.gas import init_code_cost - data_cost = 0 - + zero_bytes = 0 for byte in tx.data: if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO + zero_bytes += 1 + + tokens_in_calldata = zero_bytes + (len(tx.data) - zero_bytes) * 4 + + data_cost = Uint(tokens_in_calldata) * STANDARD_CALLDATA_TOKEN_COST if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) @@ -266,7 +271,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + auth_cost = Uint(0) + if isinstance(tx, SetCodeTransaction): + auth_cost += Uint(PER_EMPTY_ACCOUNT_COST * len(tx.authorizations)) + + return ( + Uint( + TX_BASE_COST + + data_cost + + create_cost + + access_list_cost + + auth_cost + ), + Uint(tokens_in_calldata), + ) def recover_sender(chain_id: U64, tx: Transaction) -> Address: diff --git a/whitelist.txt b/whitelist.txt index 6dfd7ae214..48b50366b4 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -12,12 +12,14 @@ Bytes64 Bytes8 Bytes256 Bytes0 +calldata copyreg copytree coinbase coincurve crypto E501 +EIP encodings endian eth From 66a935bee07f316fd2a0a2aca5d0e82e29a3dc4c Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:53:14 +0100 Subject: [PATCH 10/70] Implement EIP-7691 --- src/ethereum/prague/fork.py | 2 +- src/ethereum/prague/vm/gas.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index c673746cda..47ddba0ad1 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -83,7 +83,7 @@ "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) SYSTEM_TRANSACTION_GAS = Uint(30000000) -MAX_BLOB_GAS_PER_BLOCK = Uint(786432) +MAX_BLOB_GAS_PER_BLOCK = Uint(1179648) VERSIONED_HASH_VERSION_KZG = b"\x01" WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py index 32e35cdd0d..f01e35b3a5 100644 --- a/src/ethereum/prague/vm/gas.py +++ b/src/ethereum/prague/vm/gas.py @@ -68,10 +68,10 @@ GAS_BLOBHASH_OPCODE = Uint(3) GAS_POINT_EVALUATION = Uint(50000) -TARGET_BLOB_GAS_PER_BLOCK = U64(393216) +TARGET_BLOB_GAS_PER_BLOCK = U64(786432) GAS_PER_BLOB = Uint(2**17) MIN_BLOB_GASPRICE = Uint(1) -BLOB_GASPRICE_UPDATE_FRACTION = Uint(3338477) +BLOB_GASPRICE_UPDATE_FRACTION = Uint(5007716) GAS_BLS_G1_ADD = Uint(375) GAS_BLS_G1_MUL = Uint(12000) From 90b1834022e966f45ca052bdfe8371931c85cd1e Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 13:58:05 +0100 Subject: [PATCH 11/70] run tests --- tests/helpers/__init__.py | 5 +++ tests/helpers/load_state_tests.py | 3 ++ tests/prague/test_evm_tools.py | 30 ++++++++++++---- tests/prague/test_rlp.py | 2 ++ tests/prague/test_state_transition.py | 52 +++++++++++++++++---------- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 41cfd188a7..06d833b7bb 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -15,4 +15,9 @@ "commit_hash": "a0e8482", "fixture_path": "tests/fixtures/ethereum_tests", }, + "latest_fork_tests": { + "url": "https://github.com/gurukamath/latest_fork_tests.git", + "commit_hash": "e7ccbb6", + "fixture_path": "tests/fixtures/latest_fork_tests", + }, } diff --git a/tests/helpers/load_state_tests.py b/tests/helpers/load_state_tests.py index eca1953ae3..94ada0bffa 100644 --- a/tests/helpers/load_state_tests.py +++ b/tests/helpers/load_state_tests.py @@ -46,6 +46,9 @@ def run_blockchain_st_test(test_case: Dict, load: Load) -> None: if hasattr(genesis_header, "withdrawals_root"): parameters.append(()) + if hasattr(genesis_header, "requests_root"): + parameters.append(()) + genesis_block = load.fork.Block(*parameters) genesis_header_hash = hex_to_bytes(json_data["genesisBlockHeader"]["hash"]) diff --git a/tests/prague/test_evm_tools.py b/tests/prague/test_evm_tools.py index 781f77bb2b..3879ee1d65 100644 --- a/tests/prague/test_evm_tools.py +++ b/tests/prague/test_evm_tools.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Dict +from typing import Dict, Generator, Tuple import pytest @@ -25,17 +25,35 @@ "CALLBlake2f", "loopExp", "loopMul", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_non-degeneracy-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_bilinearity-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(G1,-G2)=e(-G1,G2)-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(abG1,G2)-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(G1,abG2)-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-inf_pair-]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-multi_inf_pair-]", ) +test_dirs = ( + "tests/fixtures/latest_fork_tests/state_tests/prague/eip2537_bls_12_381_precompiles", + "tests/fixtures/latest_fork_tests/state_tests/prague/eip7702_set_code_tx", + "tests/fixtures/latest_fork_tests/state_tests/prague/eip7623_increase_calldata_cost", +) + + +def fetch_temporary_tests(test_dirs: Tuple[str, ...]) -> Generator: + for test_dir in test_dirs: + yield from fetch_evm_tools_tests( + test_dir, + FORK_NAME, + SLOW_TESTS, + ) + @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_temporary_tests(test_dirs), ids=idfn, ) def test_evm_tools(test_case: Dict) -> None: diff --git a/tests/prague/test_rlp.py b/tests/prague/test_rlp.py index 3a76f83b3d..2dc25b675d 100644 --- a/tests/prague/test_rlp.py +++ b/tests/prague/test_rlp.py @@ -22,6 +22,7 @@ hash4 = keccak256(b"foobar") hash5 = keccak256(b"quux") hash6 = keccak256(b"foobarbaz") +hash7 = keccak256(b"quuxbaz") address1 = hex_to_address("0x00000000219ab540356cbb839cbe05303d7705fa") address2 = hex_to_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") @@ -105,6 +106,7 @@ parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), blob_gas_used=U64(7), excess_blob_gas=U64(8), + requests_hash=hash7, ) block = Block( diff --git a/tests/prague/test_state_transition.py b/tests/prague/test_state_transition.py index 60e34a24ed..f47d9f6a66 100644 --- a/tests/prague/test_state_transition.py +++ b/tests/prague/test_state_transition.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Dict +from typing import Dict, Generator, Tuple import pytest @@ -11,11 +11,11 @@ run_blockchain_st_test, ) -fetch_cancun_tests = partial(fetch_state_test_files, network="Prague") +fetch_prague_tests = partial(fetch_state_test_files, network="Prague") FIXTURES_LOADER = Load("Prague", "prague") -run_cancun_blockchain_st_tests = partial( +run_prague_blockchain_st_tests = partial( run_blockchain_st_test, load=FIXTURES_LOADER ) @@ -34,8 +34,8 @@ "stTimeConsuming/static_Call50000_sha256.json", "vmPerformance/loopExp.json", "vmPerformance/loopMul.json", - "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Cancun", - "CALLBlake2f_d9g0v0_Cancun", + "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Prague", + "CALLBlake2f_d9g0v0_Prague", "CALLCODEBlake2f_d9g0v0", # GeneralStateTests "stRandom/randomStatetest177.json", @@ -45,6 +45,14 @@ # InvalidBlockTest "bcUncleHeaderValidity/nonceWrong.json", "bcUncleHeaderValidity/wrongMixHash.json", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_non-degeneracy-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_bilinearity-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(G1,-G2\\)=e\\(-G1,G2\\)-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(abG1,G2\\)-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(G1,abG2\\)-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-inf_pair-\\]", + "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-multi_inf_pair-\\]", + "tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes\\.py\\:\\:test_block_hashes_history\\[fork_Prague-blockchain_test-full_history_plus_one_check_blockhash_first\\]", ) # These are tests that are considered to be incorrect, @@ -58,7 +66,7 @@ # InvalidBlockTest "bcForgedTest", "bcMultiChainTest", - "GasLimitHigherThan2p63m1_Cancun", + "GasLimitHigherThan2p63m1_Prague", ) # All tests that recursively create a large number of frames (50000) @@ -75,31 +83,39 @@ ) fetch_state_tests = partial( - fetch_cancun_tests, - test_dir, + fetch_prague_tests, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) -@pytest.mark.parametrize( - "test_case", - fetch_state_tests(), - ids=idfn, +# Run temporary test fixtures for Prague +test_dirs = ( + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7002_el_triggerable_withdrawals", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip6110_deposits", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7251_consolidations", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7685_general_purpose_el_requests", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip2537_bls_12_381_precompiles", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip2935_historical_block_hashes_from_state", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7702_set_code_tx", + "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7623_increase_calldata_cost", ) -def test_general_state_tests(test_case: Dict) -> None: - run_cancun_blockchain_st_tests(test_case) -# Run execution-spec-generated-tests -test_dir = f"{ETHEREUM_SPEC_TESTS_PATH}/fixtures/withdrawals" +def fetch_temporary_tests(test_dirs: Tuple[str, ...]) -> Generator: + """ + Fetch the relevant tests for a particular EIP-Implementation + from among the temporary fixtures from ethereum-spec-tests. + """ + for test_dir in test_dirs: + yield from fetch_state_tests(test_dir) @pytest.mark.parametrize( "test_case", - fetch_cancun_tests(test_dir), + fetch_temporary_tests(test_dirs), ids=idfn, ) def test_execution_specs_generated_tests(test_case: Dict) -> None: - run_cancun_blockchain_st_tests(test_case) + run_prague_blockchain_st_tests(test_case) From 6d425478acd4aee9fb6487181a977dc7bc09a6df Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 14:00:36 +0100 Subject: [PATCH 12/70] catch invalid signatures --- src/ethereum/crypto/elliptic_curve.py | 31 +++++++++++++++++-- .../vm/precompiled_contracts/ecrecover.py | 3 +- whitelist.txt | 2 ++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ethereum/crypto/elliptic_curve.py b/src/ethereum/crypto/elliptic_curve.py index 3e5e5dbdc3..76970900f2 100644 --- a/src/ethereum/crypto/elliptic_curve.py +++ b/src/ethereum/crypto/elliptic_curve.py @@ -9,9 +9,15 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256 +from ethereum.exceptions import InvalidSignatureError + from .finite_field import Field from .hash import Hash32 +SECP256K1B = U256(7) +SECP256K1P = U256( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +) SECP256K1N = U256( 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 ) @@ -40,6 +46,17 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes: public_key : `ethereum.base_types.Bytes` Recovered public key. """ + is_square = pow( + pow(r, U256(3), SECP256K1P) + SECP256K1B, + (SECP256K1P - U256(1)) // U256(2), + SECP256K1P, + ) + + if is_square != 1: + raise InvalidSignatureError( + "r is not the x-coordinate of a point on the secp256k1 curve" + ) + r_bytes = r.to_be_bytes32() s_bytes = s.to_be_bytes32() @@ -47,9 +64,17 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes: signature[32 - len(r_bytes) : 32] = r_bytes signature[64 - len(s_bytes) : 64] = s_bytes signature[64] = v - public_key = coincurve.PublicKey.from_signature_and_message( - bytes(signature), msg_hash, hasher=None - ) + + # If the recovery algorithm returns the point at infinity, + # the signature is considered invalid + # the below function will raise a ValueError. + try: + public_key = coincurve.PublicKey.from_signature_and_message( + bytes(signature), msg_hash, hasher=None + ) + except ValueError as e: + raise InvalidSignatureError from e + public_key = public_key.format(compressed=False)[1:] return public_key diff --git a/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py b/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/prague/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/whitelist.txt b/whitelist.txt index 48b50366b4..8281e2ff3b 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -66,6 +66,8 @@ U8 ulen secp256k1 secp256k1n +secp256k1p +secp256k1b statetest subclasses iadd From ed4d89d80c2ed8fa0140d292375771762e68e734 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 14:02:49 +0100 Subject: [PATCH 13/70] add state test flag --- .../evm_tools/t8n/__init__.py | 7 +++++-- src/ethereum_spec_tools/evm_tools/t8n/env.py | 19 ++++++++++++------- tests/helpers/load_evm_tools_tests.py | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 1868d45717..c835147d4d 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -330,7 +330,7 @@ def apply_body(self) -> None: if ( self.fork.is_after_fork("ethereum.cancun") - and self.env.parent_beacon_block_root is not None + and not self.options.state_test ): self.fork.process_system_transaction( self.fork.BEACON_ROOTS_ADDRESS, @@ -414,7 +414,10 @@ def apply_body(self) -> None: logs_hash = keccak256(rlp.encode(block_logs)) - if self.fork.is_after_fork("ethereum.shanghai"): + if ( + self.fork.is_after_fork("ethereum.shanghai") + and not self.options.state_test + ): withdrawals_trie = self.fork.Trie(secured=False, default=None) for i, wd in enumerate(self.env.withdrawals): diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index 6d272788a7..d1cff3975a 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -75,13 +75,15 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): self.read_ommers(data, t8n) self.read_withdrawals(data, t8n) + self.parent_beacon_block_root = None if t8n.fork.is_after_fork("ethereum.cancun"): - parent_beacon_block_root_hex = data.get("parentBeaconBlockRoot") - self.parent_beacon_block_root = ( - Bytes32(hex_to_bytes(parent_beacon_block_root_hex)) - if parent_beacon_block_root_hex is not None - else None - ) + if not t8n.options.state_test: + parent_beacon_block_root_hex = data["parentBeaconBlockRoot"] + self.parent_beacon_block_root = ( + Bytes32(hex_to_bytes(parent_beacon_block_root_hex)) + if parent_beacon_block_root_hex is not None + else None + ) self.read_excess_blob_gas(data, t8n) def read_excess_blob_gas(self, data: Any, t8n: "T8N") -> None: @@ -237,7 +239,10 @@ def read_block_hashes(self, data: Any, t8n: "T8N") -> None: Read the block hashes. Returns a maximum of 256 block hashes. """ self.parent_hash = None - if t8n.fork.is_after_fork("ethereum.prague"): + if ( + t8n.fork.is_after_fork("ethereum.prague") + and not t8n.options.state_test + ): self.parent_hash = Hash32(hex_to_bytes(data["parentHash"])) # Read the block hashes diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index f02f77c91d..82ff9abe37 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -122,6 +122,7 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: "stdin", "--state.fork", f"{fork_name}", + "--state-test", ] t8n_options = parser.parse_args(t8n_args) From 3f6a7172ca63b9fa9ce755164ffeef9abbea4e43 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 23 Jan 2025 14:48:45 +0100 Subject: [PATCH 14/70] run only prague tests --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 647714587b..7df4d42969 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ commands = pytest \ -m "not slow" \ -n auto --maxprocesses 5 \ + tests/prague \ --cov=ethereum --cov-report=term --cov-report "xml:{toxworkdir}/coverage.xml" \ --ignore-glob='tests/fixtures/*' \ --basetemp="{temp_dir}/pytest" @@ -38,6 +39,7 @@ commands = --disable-warnings \ -m "not slow" \ -n auto --maxprocesses 2 \ + tests/prague \ --ignore-glob='tests/fixtures/*' \ --basetemp="{temp_dir}/pytest" @@ -51,6 +53,7 @@ commands = pytest \ -m "not slow and not evm_tools" \ -n auto --maxprocesses 5 \ + tests/prague \ --ignore-glob='tests/fixtures/*' \ --ignore-glob='tests/test_t8n.py' \ --basetemp="{temp_dir}/pytest" \ From 3d6fa3c6beabc36298a18d6f59bd36b2811e3c82 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Fri, 24 Jan 2025 09:27:42 +0100 Subject: [PATCH 15/70] re-factor validate_transaction --- src/ethereum/prague/fork.py | 14 ++------- src/ethereum/prague/transactions.py | 46 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 47ddba0ad1..f18c28d11d 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -46,15 +46,12 @@ state_root, ) from .transactions import ( - FLOOR_CALLDATA_COST, - TX_BASE_COST, AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, SetCodeTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -955,8 +952,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -968,8 +964,6 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) @@ -1021,13 +1015,9 @@ def process_transaction( ) execution_gas_used -= gas_refund - # EIP-7623 floor price (note: no EVM costs) - floor_gas_cost = Uint( - tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST - ) # Transactions with less execution_gas_used than the floor pay at the # floor cost. - total_gas_used = max(execution_gas_used, floor_gas_cost) + total_gas_used = max(execution_gas_used, calldata_floor_gas_cost) output.gas_left = tx.gas - total_gas_used gas_refund_amount = output.gas_left * env.gas_price diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index 00594c790c..f10f954fab 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address, Authorization, VersionedHash @@ -176,7 +176,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ Verifies a transaction. @@ -198,21 +198,28 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost charged to the transaction + based on the calldata size. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ from .vm.interpreter import MAX_CODE_SIZE - intrinsic_gas, tokens_in_calldata = calculate_intrinsic_cost(tx) - gas_floor = Uint(tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST) - if max(intrinsic_gas, gas_floor) > tx.gas: - return False + intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) + if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + raise InvalidBlock if U256(tx.nonce) >= U256(U64.MAX_VALUE): - return False + raise InvalidBlock if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - return False + raise InvalidBlock - return True + return intrinsic_gas, calldata_floor_gas_cost def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: @@ -235,10 +242,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. - tokens_in_calldata : `ethereum.base_types.Uint` - The eip-7623 calldata tokens used by the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost used by the transaction + based on the calldata size. """ from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST from .vm.gas import init_code_cost @@ -248,9 +256,13 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: if byte == 0: zero_bytes += 1 - tokens_in_calldata = zero_bytes + (len(tx.data) - zero_bytes) * 4 + tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4) + # EIP-7623 floor price (note: no EVM costs) + calldata_floor_gas_cost = ( + tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST + ) - data_cost = Uint(tokens_in_calldata) * STANDARD_CALLDATA_TOKEN_COST + data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) @@ -283,7 +295,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: + access_list_cost + auth_cost ), - Uint(tokens_in_calldata), + calldata_floor_gas_cost, ) From 98c9a473b30e5dfac855d7e79a14ecadb1735dc9 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:11:40 -0500 Subject: [PATCH 16/70] change type of branch node subnodes (#1066) (#1095) --- src/ethereum/prague/trie.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/ethereum/prague/trie.py b/src/ethereum/prague/trie.py index 03ae159be7..c81ef86b61 100644 --- a/src/ethereum/prague/trie.py +++ b/src/ethereum/prague/trie.py @@ -24,14 +24,17 @@ MutableMapping, Optional, Sequence, + Tuple, TypeVar, Union, + cast, ) from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import assert_type from ethereum.cancun import trie as previous_trie from ethereum.crypto.hash import keccak256 @@ -95,12 +98,32 @@ class ExtensionNode: subnode: rlp.Extended +BranchSubnodes = Tuple[ + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, +] + + @slotted_freezable @dataclass class BranchNode: """Branch node in the Merkle Trie""" - subnodes: List[rlp.Extended] + subnodes: BranchSubnodes value: rlp.Extended @@ -140,7 +163,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: node.subnode, ) elif isinstance(node, BranchNode): - unencoded = node.subnodes + [node.value] + unencoded = list(node.subnodes) + [node.value] else: raise AssertionError(f"Invalid internal node type {type(node)}!") @@ -461,10 +484,11 @@ def patricialize( else: branches[key[level]][key] = obj[key] + subnodes = tuple( + encode_internal_node(patricialize(branches[k], level + Uint(1))) + for k in range(16) + ) return BranchNode( - [ - encode_internal_node(patricialize(branches[k], level + Uint(1))) - for k in range(16) - ], + cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), value, ) From 7fe0097b971e3b5f7e1313e07dc753b49ab8f368 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Sat, 25 Jan 2025 13:47:05 -0500 Subject: [PATCH 17/70] update storage trie type (#1070 #1071) --- src/ethereum/prague/state.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py index 1cb9cdcdc7..eae92ed528 100644 --- a/src/ethereum/prague/state.py +++ b/src/ethereum/prague/state.py @@ -19,7 +19,7 @@ from dataclasses import dataclass, field from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify from ethereum_types.numeric import U256, Uint @@ -37,12 +37,13 @@ class State: _main_trie: Trie[Address, Optional[Account]] = field( default_factory=lambda: Trie(secured=True, default=None) ) - _storage_tries: Dict[Address, Trie[Bytes, U256]] = field( + _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( default_factory=dict ) _snapshots: List[ Tuple[ - Trie[Address, Optional[Account]], Dict[Address, Trie[Bytes, U256]] + Trie[Address, Optional[Account]], + Dict[Address, Trie[Bytes32, U256]], ] ] = field(default_factory=list) created_accounts: Set[Address] = field(default_factory=set) @@ -55,8 +56,8 @@ class TransientStorage: within a transaction. """ - _tries: Dict[Address, Trie[Bytes, U256]] = field(default_factory=dict) - _snapshots: List[Dict[Address, Trie[Bytes, U256]]] = field( + _tries: Dict[Address, Trie[Bytes32, U256]] = field(default_factory=dict) + _snapshots: List[Dict[Address, Trie[Bytes32, U256]]] = field( default_factory=list ) @@ -261,7 +262,7 @@ def mark_account_created(state: State, address: Address) -> None: state.created_accounts.add(address) -def get_storage(state: State, address: Address, key: Bytes) -> U256: +def get_storage(state: State, address: Address, key: Bytes32) -> U256: """ Get a value at a storage key on an account. Returns `U256(0)` if the storage key has not been set previously. @@ -291,7 +292,7 @@ def get_storage(state: State, address: Address, key: Bytes) -> U256: def set_storage( - state: State, address: Address, key: Bytes, value: U256 + state: State, address: Address, key: Bytes32, value: U256 ) -> None: """ Set a value at a storage key on an account. Setting to `U256(0)` deletes @@ -626,7 +627,7 @@ def write_code(sender: Account) -> None: modify_state(state, address, write_code) -def get_storage_original(state: State, address: Address, key: Bytes) -> U256: +def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: """ Get the original value in a storage slot i.e. the value before the current transaction began. This function reads the value from the snapshots taken @@ -660,7 +661,7 @@ def get_storage_original(state: State, address: Address, key: Bytes) -> U256: def get_transient_storage( - transient_storage: TransientStorage, address: Address, key: Bytes + transient_storage: TransientStorage, address: Address, key: Bytes32 ) -> U256: """ Get a value at a storage key on an account from transient storage. @@ -691,7 +692,7 @@ def get_transient_storage( def set_transient_storage( transient_storage: TransientStorage, address: Address, - key: Bytes, + key: Bytes32, value: U256, ) -> None: """ From 57b8ad3aa3a94e80fc53b6dbd5ba8ce721fb516c Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Mon, 3 Feb 2025 08:44:19 +0100 Subject: [PATCH 18/70] port #1098 and #1011 to `forks/prague` (#1102) * port #1098 to prague * port #1011 to prague --- src/ethereum/prague/fork.py | 10 +++++++--- src/ethereum/prague/state.py | 7 ++++--- src/ethereum/prague/vm/__init__.py | 3 ++- src/ethereum/prague/vm/interpreter.py | 9 +++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index f18c28d11d..32fbb8efac 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -20,7 +20,11 @@ from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.exceptions import ( + EthereumException, + InvalidBlock, + InvalidSenderError, +) from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -453,7 +457,7 @@ def check_transaction( def make_receipt( tx: Transaction, - error: Optional[Exception], + error: Optional[EthereumException], cumulative_gas_used: Uint, logs: Tuple[Log, ...], ) -> Union[Bytes, Receipt]: @@ -925,7 +929,7 @@ def process_general_purpose_requests( def process_transaction( env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[Exception]]: +) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: """ Execute a transaction against the provided environment. diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py index eae92ed528..4b9c755a74 100644 --- a/src/ethereum/prague/state.py +++ b/src/ethereum/prague/state.py @@ -16,8 +16,9 @@ There is a distinction between an account that does not exist and `EMPTY_ACCOUNT`. """ + from dataclasses import dataclass, field -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -719,7 +720,7 @@ def set_transient_storage( def destroy_touched_empty_accounts( - state: State, touched_accounts: Iterable[Address] + state: State, touched_accounts: Set[Address] ) -> None: """ Destroy all touched accounts that are empty. @@ -727,7 +728,7 @@ def destroy_touched_empty_accounts( ---------- state: `State` The current state. - touched_accounts: `Iterable[Address]` + touched_accounts: `Set[Address]` All the accounts that have been touched in the current transaction. """ for address in touched_accounts: diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index e73dba47bd..e2abe20d98 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -20,6 +20,7 @@ from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 +from ethereum.exceptions import EthereumException from ..blocks import Log from ..fork_types import Address, Authorization, VersionedHash @@ -95,7 +96,7 @@ class Evm: accounts_to_delete: Set[Address] touched_accounts: Set[Address] return_data: Bytes - error: Optional[Exception] + error: Optional[EthereumException] accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index df02758171..d105142452 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -12,11 +12,12 @@ A straightforward interpreter that executes EVM code. """ from dataclasses import dataclass -from typing import Iterable, Optional, Set, Tuple, Union +from typing import Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint, ulen +from ethereum.exceptions import EthereumException from ethereum.trace import ( EvmStop, OpEnd, @@ -83,10 +84,10 @@ class MessageCallOutput: gas_left: Uint refund_counter: U256 - logs: Union[Tuple[()], Tuple[Log, ...]] + logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Iterable[Address] - error: Optional[Exception] + touched_accounts: Set[Address] + error: Optional[EthereumException] return_data: Bytes From 5c74ce0a57dc61e4eae919a23ca225762d9c1a2f Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:41:53 +0100 Subject: [PATCH 19/70] update EXTCODE* behavior in 7702 (#1094) See https://github.com/ethereum/EIPs/pull/9248/files --- src/ethereum/prague/vm/eoa_delegation.py | 1 - src/ethereum/prague/vm/instructions/environment.py | 7 ------- 2 files changed, 8 deletions(-) diff --git a/src/ethereum/prague/vm/eoa_delegation.py b/src/ethereum/prague/vm/eoa_delegation.py index b06cd144eb..8728375a25 100644 --- a/src/ethereum/prague/vm/eoa_delegation.py +++ b/src/ethereum/prague/vm/eoa_delegation.py @@ -21,7 +21,6 @@ SET_CODE_TX_MAGIC = b"\x05" EOA_DELEGATION_MARKER = b"\xEF\x01\x00" -EOA_DELEGATION_SENTINEL = b"\xEF\x01" EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) EOA_DELEGATED_CODE_LENGTH = 23 PER_EMPTY_ACCOUNT_COST = 25000 diff --git a/src/ethereum/prague/vm/instructions/environment.py b/src/ethereum/prague/vm/instructions/environment.py index 08d6e9ae0d..6a524f1496 100644 --- a/src/ethereum/prague/vm/instructions/environment.py +++ b/src/ethereum/prague/vm/instructions/environment.py @@ -21,7 +21,6 @@ from ...fork_types import EMPTY_ACCOUNT from ...state import get_account from ...utils.address import to_address -from ...vm.eoa_delegation import EOA_DELEGATION_SENTINEL, is_valid_delegation from ...vm.memory import buffer_read, memory_write from .. import Evm from ..exceptions import OutOfBoundsRead @@ -353,8 +352,6 @@ def extcodesize(evm: Evm) -> None: # OPERATION code = get_account(evm.env.state, address).code - if is_valid_delegation(code): - code = EOA_DELEGATION_SENTINEL codesize = U256(len(code)) push(evm.stack, codesize) @@ -397,8 +394,6 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code - if is_valid_delegation(code): - code = EOA_DELEGATION_SENTINEL value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -490,8 +485,6 @@ def extcodehash(evm: Evm) -> None: codehash = U256(0) else: code = account.code - if is_valid_delegation(code): - code = EOA_DELEGATION_SENTINEL codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) From 39125dc10574b5dd1be43d01b869b8b7511330d2 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 30 Jan 2025 10:50:43 -0600 Subject: [PATCH 20/70] Update EIP-2935, EIP-7002, EIP-7251 system contract addresses (#1100) --- src/ethereum/prague/fork.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 32fbb8efac..9c149f65d7 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -88,13 +88,13 @@ VERSIONED_HASH_VERSION_KZG = b"\x01" WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA" + "0x00000961Ef480Eb55e80D19ad83579A64c007002" ) CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x00431F263cE400f4455c2dCf564e53007Ca4bbBb" + "0x0000BBdDc7CE488642fb579F8B00f3a590007251" ) HISTORY_STORAGE_ADDRESS = hex_to_address( - "0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC" + "0x0000F90827F1C53a10cb7A02335B175320002935" ) HISTORY_SERVE_WINDOW = 8192 From 69113623f509dfda3dd3cf1b7877e7bd0011a239 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Wed, 5 Feb 2025 10:24:03 +0100 Subject: [PATCH 21/70] run pectra-devnet-6@v1.0.0 tests --- tests/helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 06d833b7bb..b551825535 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -17,7 +17,7 @@ }, "latest_fork_tests": { "url": "https://github.com/gurukamath/latest_fork_tests.git", - "commit_hash": "e7ccbb6", + "commit_hash": "e15efcb", "fixture_path": "tests/fixtures/latest_fork_tests", }, } From 6a2d8095ca7439a622630da3d583a7d933aecca9 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:59:32 +0100 Subject: [PATCH 22/70] Backport changes from prague (#1113) * enable testing for all forks * catch invalid signatures * refactor validate_transaction * define accessed_addresses at the start * update variable name to BLOB_BASE_FEE_UPDATE_FRACTION * refund_counter definition * create encode_receipt function * create process_system_transaction * update gas calc * explicitly define code_address in call* * fix decode_receipt --- src/ethereum/arrow_glacier/blocks.py | 31 ++- src/ethereum/arrow_glacier/fork.py | 15 +- src/ethereum/arrow_glacier/transactions.py | 24 ++- src/ethereum/arrow_glacier/utils/message.py | 8 +- .../vm/instructions/environment.py | 28 +-- .../arrow_glacier/vm/instructions/system.py | 8 +- src/ethereum/arrow_glacier/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/berlin/blocks.py | 24 ++- src/ethereum/berlin/fork.py | 13 +- src/ethereum/berlin/transactions.py | 24 ++- src/ethereum/berlin/utils/message.py | 8 +- .../berlin/vm/instructions/environment.py | 28 +-- src/ethereum/berlin/vm/instructions/system.py | 8 +- src/ethereum/berlin/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/byzantium/fork.py | 12 +- src/ethereum/byzantium/transactions.py | 24 ++- .../byzantium/vm/instructions/environment.py | 4 +- .../byzantium/vm/instructions/system.py | 10 +- src/ethereum/byzantium/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/cancun/blocks.py | 34 +++- src/ethereum/cancun/fork.py | 177 ++++++++++++------ src/ethereum/cancun/transactions.py | 26 ++- src/ethereum/cancun/utils/message.py | 8 +- src/ethereum/cancun/vm/gas.py | 4 +- .../cancun/vm/instructions/environment.py | 28 +-- src/ethereum/cancun/vm/instructions/system.py | 8 +- src/ethereum/cancun/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/constantinople/fork.py | 12 +- src/ethereum/constantinople/transactions.py | 24 ++- .../vm/instructions/environment.py | 8 +- .../constantinople/vm/instructions/system.py | 10 +- src/ethereum/constantinople/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/dao_fork/fork.py | 12 +- src/ethereum/dao_fork/transactions.py | 24 ++- .../dao_fork/vm/instructions/environment.py | 4 +- .../dao_fork/vm/instructions/system.py | 5 +- src/ethereum/dao_fork/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/frontier/fork.py | 12 +- src/ethereum/frontier/transactions.py | 24 ++- .../frontier/vm/instructions/environment.py | 4 +- .../frontier/vm/instructions/system.py | 5 +- src/ethereum/frontier/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/gray_glacier/blocks.py | 31 ++- src/ethereum/gray_glacier/fork.py | 15 +- src/ethereum/gray_glacier/transactions.py | 24 ++- src/ethereum/gray_glacier/utils/message.py | 8 +- .../vm/instructions/environment.py | 28 +-- .../gray_glacier/vm/instructions/system.py | 8 +- src/ethereum/gray_glacier/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/homestead/fork.py | 12 +- src/ethereum/homestead/transactions.py | 24 ++- .../homestead/vm/instructions/environment.py | 4 +- .../homestead/vm/instructions/system.py | 5 +- src/ethereum/homestead/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/istanbul/fork.py | 12 +- src/ethereum/istanbul/transactions.py | 24 ++- .../istanbul/vm/instructions/environment.py | 8 +- .../istanbul/vm/instructions/system.py | 10 +- src/ethereum/istanbul/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/london/blocks.py | 31 ++- src/ethereum/london/fork.py | 15 +- src/ethereum/london/transactions.py | 24 ++- src/ethereum/london/utils/message.py | 8 +- .../london/vm/instructions/environment.py | 28 +-- src/ethereum/london/vm/instructions/system.py | 8 +- src/ethereum/london/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/muir_glacier/fork.py | 12 +- src/ethereum/muir_glacier/transactions.py | 24 ++- .../vm/instructions/environment.py | 8 +- .../muir_glacier/vm/instructions/system.py | 10 +- src/ethereum/muir_glacier/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/paris/blocks.py | 31 ++- src/ethereum/paris/fork.py | 15 +- src/ethereum/paris/transactions.py | 24 ++- src/ethereum/paris/utils/message.py | 8 +- .../paris/vm/instructions/environment.py | 28 +-- src/ethereum/paris/vm/instructions/system.py | 8 +- src/ethereum/paris/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/prague/vm/gas.py | 4 +- src/ethereum/shanghai/blocks.py | 31 ++- src/ethereum/shanghai/fork.py | 15 +- src/ethereum/shanghai/transactions.py | 26 ++- src/ethereum/shanghai/utils/message.py | 8 +- .../shanghai/vm/instructions/environment.py | 28 +-- .../shanghai/vm/instructions/system.py | 8 +- src/ethereum/shanghai/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/spurious_dragon/fork.py | 12 +- src/ethereum/spurious_dragon/transactions.py | 24 ++- .../vm/instructions/environment.py | 4 +- .../spurious_dragon/vm/instructions/system.py | 5 +- .../spurious_dragon/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- src/ethereum/tangerine_whistle/fork.py | 12 +- .../tangerine_whistle/transactions.py | 24 ++- .../vm/instructions/environment.py | 4 +- .../vm/instructions/system.py | 5 +- .../tangerine_whistle/vm/interpreter.py | 4 +- .../vm/precompiled_contracts/ecrecover.py | 3 +- tests/berlin/test_transaction.py | 14 +- tests/byzantium/test_transaction.py | 14 +- tests/constantinople/test_transaction.py | 13 +- tests/frontier/test_transaction.py | 14 +- tests/homestead/test_transaction.py | 14 +- tests/istanbul/test_transaction.py | 14 +- tests/london/test_transaction.py | 14 +- tests/spurious_dragon/test_transaction.py | 13 +- tests/tangerine_whistle/test_transaction.py | 13 +- tox.ini | 3 - 122 files changed, 1023 insertions(+), 618 deletions(-) diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index 87c9acac7f..38002554ca 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -11,13 +11,19 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -80,3 +86,26 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index efd0a4696a..a01cb829b9 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -28,7 +28,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -46,7 +46,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -476,12 +475,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -779,8 +773,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -799,7 +792,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index bf3acc28ab..1e7680501b 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -119,7 +119,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -141,14 +141,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/arrow_glacier/utils/message.py b/src/ethereum/arrow_glacier/utils/message.py index 6cdf44aae8..c1268d4f6a 100644 --- a/src/ethereum/arrow_glacier/utils/message.py +++ b/src/ethereum/arrow_glacier/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.arrow_glacier.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/arrow_glacier/vm/instructions/environment.py b/src/ethereum/arrow_glacier/vm/instructions/environment.py index 33d8396a48..dc59e168bd 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/environment.py +++ b/src/ethereum/arrow_glacier/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/arrow_glacier/vm/instructions/system.py b/src/ethereum/arrow_glacier/vm/instructions/system.py index 4ace48ad27..961b3e0c00 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/arrow_glacier/vm/instructions/system.py @@ -342,6 +342,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -373,7 +375,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -605,6 +607,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -622,7 +626,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/arrow_glacier/vm/interpreter.py b/src/ethereum/arrow_glacier/vm/interpreter.py index ac38967b9c..469e37d3a2 100644 --- a/src/ethereum/arrow_glacier/vm/interpreter.py +++ b/src/ethereum/arrow_glacier/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/arrow_glacier/vm/precompiled_contracts/ecrecover.py b/src/ethereum/arrow_glacier/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/arrow_glacier/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/arrow_glacier/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index df486c7a9d..ce975fa4db 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -11,13 +11,14 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import AccessListTransaction, LegacyTransaction, Transaction @slotted_freezable @@ -79,3 +80,24 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] == 1 + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 46376e11d0..20cbb4ffc4 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -28,7 +28,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -45,7 +45,6 @@ AccessListTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -378,10 +377,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -673,8 +669,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -686,7 +681,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 96a4c952cc..b1b9f7ce74 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -91,7 +91,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -113,14 +113,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/berlin/utils/message.py b/src/ethereum/berlin/utils/message.py index 80490e0735..122d127a1c 100644 --- a/src/ethereum/berlin/utils/message.py +++ b/src/ethereum/berlin/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.berlin.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/berlin/vm/instructions/environment.py b/src/ethereum/berlin/vm/instructions/environment.py index e8f2505a51..0c2e50ab2c 100644 --- a/src/ethereum/berlin/vm/instructions/environment.py +++ b/src/ethereum/berlin/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/berlin/vm/instructions/system.py b/src/ethereum/berlin/vm/instructions/system.py index d5a19b237d..5f1be5a398 100644 --- a/src/ethereum/berlin/vm/instructions/system.py +++ b/src/ethereum/berlin/vm/instructions/system.py @@ -343,6 +343,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -374,7 +376,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -616,6 +618,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -633,7 +637,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/berlin/vm/interpreter.py b/src/ethereum/berlin/vm/interpreter.py index bc7d4eb589..b8b4971d21 100644 --- a/src/ethereum/berlin/vm/interpreter.py +++ b/src/ethereum/berlin/vm/interpreter.py @@ -108,6 +108,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -127,12 +128,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/berlin/vm/precompiled_contracts/ecrecover.py b/src/ethereum/berlin/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/berlin/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/berlin/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index f21323cda2..285da2ce1f 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -41,12 +41,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -663,8 +658,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -676,7 +670,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index 142929587d..1c67eeaf02 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/byzantium/vm/instructions/environment.py b/src/ethereum/byzantium/vm/instructions/environment.py index 1a2a295c52..5a18a1c86d 100644 --- a/src/ethereum/byzantium/vm/instructions/environment.py +++ b/src/ethereum/byzantium/vm/instructions/environment.py @@ -336,8 +336,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -371,6 +372,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/byzantium/vm/instructions/system.py b/src/ethereum/byzantium/vm/instructions/system.py index ce20f3ecdd..52388abb2b 100644 --- a/src/ethereum/byzantium/vm/instructions/system.py +++ b/src/ethereum/byzantium/vm/instructions/system.py @@ -261,6 +261,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + create_gas_cost = ( Uint(0) if value == 0 or is_account_alive(evm.env.state, to) @@ -292,7 +295,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -509,6 +512,9 @@ def staticcall(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -526,7 +532,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/byzantium/vm/interpreter.py b/src/ethereum/byzantium/vm/interpreter.py index 3d2e368c9b..fb99df6f05 100644 --- a/src/ethereum/byzantium/vm/interpreter.py +++ b/src/ethereum/byzantium/vm/interpreter.py @@ -107,6 +107,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -126,12 +127,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/byzantium/vm/precompiled_contracts/ecrecover.py b/src/ethereum/byzantium/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/byzantium/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/byzantium/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index 54ef88e15f..f1ac3fd758 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -11,13 +11,20 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -98,3 +105,28 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2, 3) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index b0fb55ec47..3bbe82b69b 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -27,7 +27,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal +from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root, VersionedHash from .state import ( @@ -48,7 +48,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -64,7 +63,7 @@ calculate_excess_blob_gas, calculate_total_blob_gas, ) -from .vm.interpreter import process_message_call +from .vm.interpreter import MessageCallOutput, process_message_call BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) ELASTICITY_MULTIPLIER = Uint(2) @@ -456,14 +455,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - elif isinstance(tx, BlobTransaction): - return b"\x03" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -499,6 +491,107 @@ class ApplyBodyOutput: blob_gas_used: Uint +def process_system_transaction( + target_address: Address, + data: Bytes, + block_hashes: List[Hash32], + coinbase: Address, + block_number: Uint, + base_fee_per_gas: Uint, + block_gas_limit: Uint, + block_time: U256, + prev_randao: Bytes32, + state: State, + chain_id: U64, + excess_blob_gas: U64, +) -> MessageCallOutput: + """ + Process a system transaction. + + Parameters + ---------- + target_address : + Address of the contract to call. + data : + Data to pass to the contract. + block_hashes : + List of hashes of the previous 256 blocks. + coinbase : + Address of the block's coinbase. + block_number : + Block number. + base_fee_per_gas : + Base fee per gas. + block_gas_limit : + Gas limit of the block. + block_time : + Time the block was produced. + prev_randao : + Previous randao value. + state : + Current state. + chain_id : + ID of the chain. + excess_blob_gas : + Excess blob gas. + + Returns + ------- + system_tx_output : `MessageCallOutput` + Output of processing the system transaction. + """ + system_contract_code = get_account(state, target_address).code + + system_tx_message = Message( + caller=SYSTEM_ADDRESS, + target=target_address, + gas=SYSTEM_TRANSACTION_GAS, + value=U256(0), + data=data, + code=system_contract_code, + depth=Uint(0), + current_target=target_address, + code_address=target_address, + should_transfer_value=False, + is_static=False, + accessed_addresses=set(), + accessed_storage_keys=set(), + parent_evm=None, + ) + + system_tx_env = vm.Environment( + caller=SYSTEM_ADDRESS, + block_hashes=block_hashes, + origin=SYSTEM_ADDRESS, + coinbase=coinbase, + number=block_number, + gas_limit=block_gas_limit, + base_fee_per_gas=base_fee_per_gas, + gas_price=base_fee_per_gas, + time=block_time, + prev_randao=prev_randao, + state=state, + chain_id=chain_id, + traces=[], + excess_blob_gas=excess_blob_gas, + blob_versioned_hashes=(), + transient_storage=TransientStorage(), + ) + + system_tx_output = process_message_call(system_tx_message, system_tx_env) + + # TODO: Empty accounts in post-merge forks are impossible + # see Ethereum Improvement Proposal 7523. + # This line is only included to support invalid tests in the test suite + # and will have to be removed in the future. + # See https://github.com/ethereum/execution-specs/issues/955 + destroy_touched_empty_accounts( + system_tx_env.state, system_tx_output.touched_accounts + ) + + return system_tx_output + + def apply_body( state: State, block_hashes: List[Hash32], @@ -575,50 +668,19 @@ def apply_body( ) block_logs: Tuple[Log, ...] = () - beacon_block_roots_contract_code = get_account( - state, BEACON_ROOTS_ADDRESS - ).code - - system_tx_message = Message( - caller=SYSTEM_ADDRESS, - target=BEACON_ROOTS_ADDRESS, - gas=SYSTEM_TRANSACTION_GAS, - value=U256(0), - data=parent_beacon_block_root, - code=beacon_block_roots_contract_code, - depth=Uint(0), - current_target=BEACON_ROOTS_ADDRESS, - code_address=BEACON_ROOTS_ADDRESS, - should_transfer_value=False, - is_static=False, - accessed_addresses=set(), - accessed_storage_keys=set(), - parent_evm=None, - ) - - system_tx_env = vm.Environment( - caller=SYSTEM_ADDRESS, - origin=SYSTEM_ADDRESS, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=base_fee_per_gas, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=(), - transient_storage=TransientStorage(), - ) - - system_tx_output = process_message_call(system_tx_message, system_tx_env) - - destroy_touched_empty_accounts( - system_tx_env.state, system_tx_output.touched_accounts + process_system_transaction( + BEACON_ROOTS_ADDRESS, + parent_beacon_block_root, + block_hashes, + coinbase, + block_number, + base_fee_per_gas, + block_gas_limit, + block_time, + prev_randao, + state, + chain_id, + excess_blob_gas, ) for i, tx in enumerate(map(decode_transaction, transactions)): @@ -727,8 +789,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -740,7 +801,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index 3df84a87b7..ce8a085f75 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash @@ -149,7 +149,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -171,19 +171,25 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ from .vm.interpreter import MAX_CODE_SIZE - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - return False + raise InvalidBlock - return True + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/cancun/utils/message.py b/src/ethereum/cancun/utils/message.py index 95057a0d2d..68deb1a62f 100644 --- a/src/ethereum/cancun/utils/message.py +++ b/src/ethereum/cancun/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.cancun.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/cancun/vm/gas.py b/src/ethereum/cancun/vm/gas.py index cc3ccc7c6b..794396cbf2 100644 --- a/src/ethereum/cancun/vm/gas.py +++ b/src/ethereum/cancun/vm/gas.py @@ -71,7 +71,7 @@ TARGET_BLOB_GAS_PER_BLOCK = U64(393216) GAS_PER_BLOB = Uint(2**17) MIN_BLOB_GASPRICE = Uint(1) -BLOB_GASPRICE_UPDATE_FRACTION = Uint(3338477) +BLOB_BASE_FEE_UPDATE_FRACTION = Uint(3338477) @dataclass @@ -337,7 +337,7 @@ def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: return taylor_exponential( MIN_BLOB_GASPRICE, Uint(excess_blob_gas), - BLOB_GASPRICE_UPDATE_FRACTION, + BLOB_BASE_FEE_UPDATE_FRACTION, ) diff --git a/src/ethereum/cancun/vm/instructions/environment.py b/src/ethereum/cancun/vm/instructions/environment.py index 4ba384f9ba..6a524f1496 100644 --- a/src/ethereum/cancun/vm/instructions/environment.py +++ b/src/ethereum/cancun/vm/instructions/environment.py @@ -343,15 +343,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -382,16 +384,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -468,10 +471,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -479,7 +484,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index da47df38cf..58cec71f07 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -366,6 +366,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -397,7 +399,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -631,6 +633,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -648,7 +652,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/cancun/vm/interpreter.py b/src/ethereum/cancun/vm/interpreter.py index cfb5f32843..79a725b316 100644 --- a/src/ethereum/cancun/vm/interpreter.py +++ b/src/ethereum/cancun/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/cancun/vm/precompiled_contracts/ecrecover.py b/src/ethereum/cancun/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/cancun/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/cancun/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 1eafe4360e..e91d8565a7 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -41,12 +41,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -663,8 +658,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -676,7 +670,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index 142929587d..1c67eeaf02 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/constantinople/vm/instructions/environment.py b/src/ethereum/constantinople/vm/instructions/environment.py index 3062ffee77..af92af6891 100644 --- a/src/ethereum/constantinople/vm/instructions/environment.py +++ b/src/ethereum/constantinople/vm/instructions/environment.py @@ -338,9 +338,9 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -374,6 +374,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -457,7 +458,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/constantinople/vm/instructions/system.py b/src/ethereum/constantinople/vm/instructions/system.py index b74c3d3116..0c7608d40d 100644 --- a/src/ethereum/constantinople/vm/instructions/system.py +++ b/src/ethereum/constantinople/vm/instructions/system.py @@ -329,6 +329,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + create_gas_cost = ( Uint(0) if value == 0 or is_account_alive(evm.env.state, to) @@ -360,7 +363,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -577,6 +580,9 @@ def staticcall(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -594,7 +600,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/constantinople/vm/interpreter.py b/src/ethereum/constantinople/vm/interpreter.py index b35dd23706..80eeddc42e 100644 --- a/src/ethereum/constantinople/vm/interpreter.py +++ b/src/ethereum/constantinople/vm/interpreter.py @@ -107,6 +107,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -126,12 +127,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/constantinople/vm/precompiled_contracts/ecrecover.py b/src/ethereum/constantinople/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/constantinople/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/constantinople/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 1835a2525f..7b653c5bc2 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -39,12 +39,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -665,8 +660,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -678,7 +672,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index 57d697d615..96d825e2ba 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/dao_fork/vm/instructions/environment.py b/src/ethereum/dao_fork/vm/instructions/environment.py index 9d936e7f5f..1bce21fda7 100644 --- a/src/ethereum/dao_fork/vm/instructions/environment.py +++ b/src/ethereum/dao_fork/vm/instructions/environment.py @@ -334,8 +334,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -369,6 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/dao_fork/vm/instructions/system.py b/src/ethereum/dao_fork/vm/instructions/system.py index fd65ab1a42..1e87e93319 100644 --- a/src/ethereum/dao_fork/vm/instructions/system.py +++ b/src/ethereum/dao_fork/vm/instructions/system.py @@ -242,6 +242,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( evm.env.state, gas, to, value ) @@ -262,7 +265,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, memory_input_start_position, memory_input_size, diff --git a/src/ethereum/dao_fork/vm/interpreter.py b/src/ethereum/dao_fork/vm/interpreter.py index d9e55e4891..904c78f3cd 100644 --- a/src/ethereum/dao_fork/vm/interpreter.py +++ b/src/ethereum/dao_fork/vm/interpreter.py @@ -100,6 +100,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -116,11 +117,10 @@ def process_message_call( if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/dao_fork/vm/precompiled_contracts/ecrecover.py b/src/ethereum/dao_fork/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/dao_fork/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/dao_fork/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 46749732f4..3e96a9aa73 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -36,12 +36,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -647,8 +642,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -660,7 +654,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index a09bba38ba..396765e22c 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -40,7 +40,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -62,14 +62,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/frontier/vm/instructions/environment.py b/src/ethereum/frontier/vm/instructions/environment.py index 9d936e7f5f..1bce21fda7 100644 --- a/src/ethereum/frontier/vm/instructions/environment.py +++ b/src/ethereum/frontier/vm/instructions/environment.py @@ -334,8 +334,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -369,6 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/frontier/vm/instructions/system.py b/src/ethereum/frontier/vm/instructions/system.py index f5165e1100..a86174de7e 100644 --- a/src/ethereum/frontier/vm/instructions/system.py +++ b/src/ethereum/frontier/vm/instructions/system.py @@ -238,6 +238,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( evm.env.state, gas, to, value ) @@ -258,7 +261,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, memory_input_start_position, memory_input_size, memory_output_start_position, diff --git a/src/ethereum/frontier/vm/interpreter.py b/src/ethereum/frontier/vm/interpreter.py index 0c392d68ef..490a91b75a 100644 --- a/src/ethereum/frontier/vm/interpreter.py +++ b/src/ethereum/frontier/vm/interpreter.py @@ -100,6 +100,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -116,11 +117,10 @@ def process_message_call( if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/frontier/vm/precompiled_contracts/ecrecover.py b/src/ethereum/frontier/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/frontier/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/frontier/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index 87c9acac7f..38002554ca 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -11,13 +11,19 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -80,3 +86,26 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index a43116185b..9eadb2a99d 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -28,7 +28,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -46,7 +46,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -476,12 +475,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -779,8 +773,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -799,7 +792,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index bf3acc28ab..1e7680501b 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -119,7 +119,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -141,14 +141,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/gray_glacier/utils/message.py b/src/ethereum/gray_glacier/utils/message.py index 28091f510f..73e4bc320f 100644 --- a/src/ethereum/gray_glacier/utils/message.py +++ b/src/ethereum/gray_glacier/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.gray_glacier.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/gray_glacier/vm/instructions/environment.py b/src/ethereum/gray_glacier/vm/instructions/environment.py index 33d8396a48..dc59e168bd 100644 --- a/src/ethereum/gray_glacier/vm/instructions/environment.py +++ b/src/ethereum/gray_glacier/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/gray_glacier/vm/instructions/system.py b/src/ethereum/gray_glacier/vm/instructions/system.py index 4ace48ad27..961b3e0c00 100644 --- a/src/ethereum/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/gray_glacier/vm/instructions/system.py @@ -342,6 +342,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -373,7 +375,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -605,6 +607,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -622,7 +626,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/gray_glacier/vm/interpreter.py b/src/ethereum/gray_glacier/vm/interpreter.py index ac38967b9c..469e37d3a2 100644 --- a/src/ethereum/gray_glacier/vm/interpreter.py +++ b/src/ethereum/gray_glacier/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/gray_glacier/vm/precompiled_contracts/ecrecover.py b/src/ethereum/gray_glacier/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/gray_glacier/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/gray_glacier/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index 1419c87b6d..cf39821f14 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -36,12 +36,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -647,8 +642,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -660,7 +654,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index 57d697d615..96d825e2ba 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/homestead/vm/instructions/environment.py b/src/ethereum/homestead/vm/instructions/environment.py index 9d936e7f5f..1bce21fda7 100644 --- a/src/ethereum/homestead/vm/instructions/environment.py +++ b/src/ethereum/homestead/vm/instructions/environment.py @@ -334,8 +334,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -369,6 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/homestead/vm/instructions/system.py b/src/ethereum/homestead/vm/instructions/system.py index fd65ab1a42..1e87e93319 100644 --- a/src/ethereum/homestead/vm/instructions/system.py +++ b/src/ethereum/homestead/vm/instructions/system.py @@ -242,6 +242,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( evm.env.state, gas, to, value ) @@ -262,7 +265,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, memory_input_start_position, memory_input_size, diff --git a/src/ethereum/homestead/vm/interpreter.py b/src/ethereum/homestead/vm/interpreter.py index bc689e930e..75462961fa 100644 --- a/src/ethereum/homestead/vm/interpreter.py +++ b/src/ethereum/homestead/vm/interpreter.py @@ -100,6 +100,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -116,11 +117,10 @@ def process_message_call( if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/homestead/vm/precompiled_contracts/ecrecover.py b/src/ethereum/homestead/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/homestead/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/homestead/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 3d0b7859a2..7ecfa04c11 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -41,12 +41,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -664,8 +659,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -677,7 +671,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index 2bdb3603b9..e455e2549a 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/istanbul/vm/instructions/environment.py b/src/ethereum/istanbul/vm/instructions/environment.py index a641fe167b..fc300ca74a 100644 --- a/src/ethereum/istanbul/vm/instructions/environment.py +++ b/src/ethereum/istanbul/vm/instructions/environment.py @@ -339,9 +339,9 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -375,6 +375,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -458,7 +459,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/istanbul/vm/instructions/system.py b/src/ethereum/istanbul/vm/instructions/system.py index b74c3d3116..0c7608d40d 100644 --- a/src/ethereum/istanbul/vm/instructions/system.py +++ b/src/ethereum/istanbul/vm/instructions/system.py @@ -329,6 +329,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + create_gas_cost = ( Uint(0) if value == 0 or is_account_alive(evm.env.state, to) @@ -360,7 +363,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -577,6 +580,9 @@ def staticcall(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -594,7 +600,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/istanbul/vm/interpreter.py b/src/ethereum/istanbul/vm/interpreter.py index ed68411e7a..9eff74022b 100644 --- a/src/ethereum/istanbul/vm/interpreter.py +++ b/src/ethereum/istanbul/vm/interpreter.py @@ -108,6 +108,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -127,12 +128,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/istanbul/vm/precompiled_contracts/ecrecover.py b/src/ethereum/istanbul/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/istanbul/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/istanbul/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 87c9acac7f..38002554ca 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -11,13 +11,19 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -80,3 +86,26 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index 52087e0907..cb86d7f06b 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -28,7 +28,7 @@ ) from . import FORK_CRITERIA, vm -from .blocks import Block, Header, Log, Receipt +from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -46,7 +46,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -482,12 +481,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -785,8 +779,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -805,7 +798,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index bf3acc28ab..1e7680501b 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -119,7 +119,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -141,14 +141,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/london/utils/message.py b/src/ethereum/london/utils/message.py index f867fcab98..fcd8b1dc59 100644 --- a/src/ethereum/london/utils/message.py +++ b/src/ethereum/london/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.london.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/london/vm/instructions/environment.py b/src/ethereum/london/vm/instructions/environment.py index 33d8396a48..dc59e168bd 100644 --- a/src/ethereum/london/vm/instructions/environment.py +++ b/src/ethereum/london/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/london/vm/instructions/system.py b/src/ethereum/london/vm/instructions/system.py index 4ace48ad27..961b3e0c00 100644 --- a/src/ethereum/london/vm/instructions/system.py +++ b/src/ethereum/london/vm/instructions/system.py @@ -342,6 +342,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -373,7 +375,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -605,6 +607,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -622,7 +626,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/london/vm/interpreter.py b/src/ethereum/london/vm/interpreter.py index ac38967b9c..469e37d3a2 100644 --- a/src/ethereum/london/vm/interpreter.py +++ b/src/ethereum/london/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/london/vm/precompiled_contracts/ecrecover.py b/src/ethereum/london/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/london/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/london/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index 4f93ff7216..423b250281 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -41,12 +41,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -664,8 +659,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -677,7 +671,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index 2bdb3603b9..e455e2549a 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/muir_glacier/vm/instructions/environment.py b/src/ethereum/muir_glacier/vm/instructions/environment.py index a641fe167b..fc300ca74a 100644 --- a/src/ethereum/muir_glacier/vm/instructions/environment.py +++ b/src/ethereum/muir_glacier/vm/instructions/environment.py @@ -339,9 +339,9 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -375,6 +375,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -458,7 +459,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/muir_glacier/vm/instructions/system.py b/src/ethereum/muir_glacier/vm/instructions/system.py index b74c3d3116..0c7608d40d 100644 --- a/src/ethereum/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/muir_glacier/vm/instructions/system.py @@ -329,6 +329,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + create_gas_cost = ( Uint(0) if value == 0 or is_account_alive(evm.env.state, to) @@ -360,7 +363,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -577,6 +580,9 @@ def staticcall(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -594,7 +600,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/muir_glacier/vm/interpreter.py b/src/ethereum/muir_glacier/vm/interpreter.py index 62f47456eb..c01f17c522 100644 --- a/src/ethereum/muir_glacier/vm/interpreter.py +++ b/src/ethereum/muir_glacier/vm/interpreter.py @@ -108,6 +108,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -127,12 +128,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/muir_glacier/vm/precompiled_contracts/ecrecover.py b/src/ethereum/muir_glacier/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/muir_glacier/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/muir_glacier/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index 5bfb604816..7d2f79dd72 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -11,13 +11,19 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -80,3 +86,26 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index fbee3a9eb0..c6b3f8538c 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -27,7 +27,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -44,7 +44,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -389,12 +388,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -569,8 +563,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -588,7 +581,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 00aa12a1f5..1e7680501b 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -119,7 +119,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -141,14 +141,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/paris/utils/message.py b/src/ethereum/paris/utils/message.py index 13e5b0d306..d0a74169b0 100644 --- a/src/ethereum/paris/utils/message.py +++ b/src/ethereum/paris/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.paris.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/paris/vm/instructions/environment.py b/src/ethereum/paris/vm/instructions/environment.py index 33d8396a48..dc59e168bd 100644 --- a/src/ethereum/paris/vm/instructions/environment.py +++ b/src/ethereum/paris/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index 4ace48ad27..961b3e0c00 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -342,6 +342,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -373,7 +375,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -605,6 +607,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -622,7 +626,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/paris/vm/interpreter.py b/src/ethereum/paris/vm/interpreter.py index 6d09addd76..cb2ec7c60e 100644 --- a/src/ethereum/paris/vm/interpreter.py +++ b/src/ethereum/paris/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/paris/vm/precompiled_contracts/ecrecover.py b/src/ethereum/paris/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/paris/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/paris/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py index f01e35b3a5..624a8f86e2 100644 --- a/src/ethereum/prague/vm/gas.py +++ b/src/ethereum/prague/vm/gas.py @@ -71,7 +71,7 @@ TARGET_BLOB_GAS_PER_BLOCK = U64(786432) GAS_PER_BLOB = Uint(2**17) MIN_BLOB_GASPRICE = Uint(1) -BLOB_GASPRICE_UPDATE_FRACTION = Uint(5007716) +BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716) GAS_BLS_G1_ADD = Uint(375) GAS_BLS_G1_MUL = Uint(12000) @@ -344,7 +344,7 @@ def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: return taylor_exponential( MIN_BLOB_GASPRICE, Uint(excess_blob_gas), - BLOB_GASPRICE_UPDATE_FRACTION, + BLOB_BASE_FEE_UPDATE_FRACTION, ) diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index c76584859c..9a3f6a5927 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -11,13 +11,19 @@ from dataclasses import dataclass from typing import Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root -from .transactions import LegacyTransaction +from .transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, +) @slotted_freezable @@ -95,3 +101,26 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 86925fb6b9..d8a28becfc 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -27,7 +27,7 @@ ) from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal +from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -45,7 +45,6 @@ FeeMarketTransaction, LegacyTransaction, Transaction, - calculate_intrinsic_cost, decode_transaction, encode_transaction, recover_sender, @@ -393,12 +392,7 @@ def make_receipt( logs=logs, ) - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - else: - return receipt + return encode_receipt(tx, receipt) @dataclass @@ -591,8 +585,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -610,7 +603,7 @@ def process_transaction( effective_gas_fee = tx.gas * env.gas_price - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = ( diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index 0771da20e2..16c5a73044 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .exceptions import TransactionTypeError from .fork_types import Address @@ -119,7 +119,7 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -141,19 +141,25 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ from .vm.interpreter import MAX_CODE_SIZE - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - return False + raise InvalidBlock - return True + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/shanghai/utils/message.py b/src/ethereum/shanghai/utils/message.py index 26e950573d..2d5c3b50ef 100644 --- a/src/ethereum/shanghai/utils/message.py +++ b/src/ethereum/shanghai/utils/message.py @@ -76,6 +76,10 @@ def prepare_message( message: `ethereum.shanghai.vm.Message` Items containing contract creation or message call specific data. """ + accessed_addresses = set() + accessed_addresses.add(caller) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(preaccessed_addresses) if isinstance(target, Bytes0): current_target = compute_contract_address( caller, @@ -92,11 +96,7 @@ def prepare_message( else: raise AssertionError("Target must be address or empty bytes") - accessed_addresses = set() accessed_addresses.add(current_target) - accessed_addresses.add(caller) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) return Message( caller=caller, diff --git a/src/ethereum/shanghai/vm/instructions/environment.py b/src/ethereum/shanghai/vm/instructions/environment.py index 33d8396a48..dc59e168bd 100644 --- a/src/ethereum/shanghai/vm/instructions/environment.py +++ b/src/ethereum/shanghai/vm/instructions/environment.py @@ -340,15 +340,17 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -379,16 +381,17 @@ def extcodecopy(evm: Evm) -> None: ) if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS + copy_gas_cost + extend_memory.cost) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas( - evm, GAS_COLD_ACCOUNT_ACCESS + copy_gas_cost + extend_memory.cost - ) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -465,10 +468,12 @@ def extcodehash(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) + access_gas_cost = GAS_WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) # OPERATION account = get_account(evm.env.state, address) @@ -476,7 +481,8 @@ def extcodehash(evm: Evm) -> None: if account == EMPTY_ACCOUNT: codehash = U256(0) else: - codehash = U256.from_be_bytes(keccak256(account.code)) + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) push(evm.stack, codehash) diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 12eb5dd34f..2498d11792 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -365,6 +365,8 @@ def call(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + create_gas_cost = ( Uint(0) if is_account_alive(evm.env.state, to) or value == 0 @@ -396,7 +398,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, False, memory_input_start_position, @@ -628,6 +630,8 @@ def staticcall(evm: Evm) -> None: evm.accessed_addresses.add(to) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code_address = to + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -645,7 +649,7 @@ def staticcall(evm: Evm) -> None: U256(0), evm.message.current_target, to, - to, + code_address, True, True, memory_input_start_position, diff --git a/src/ethereum/shanghai/vm/interpreter.py b/src/ethereum/shanghai/vm/interpreter.py index 760b954168..4c4ac75e26 100644 --- a/src/ethereum/shanghai/vm/interpreter.py +++ b/src/ethereum/shanghai/vm/interpreter.py @@ -109,6 +109,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -128,12 +129,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py b/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index cb4d0b0702..420fce593e 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -37,12 +37,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -655,8 +650,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -668,7 +662,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index 142929587d..1c67eeaf02 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/spurious_dragon/vm/instructions/environment.py b/src/ethereum/spurious_dragon/vm/instructions/environment.py index 9d936e7f5f..1bce21fda7 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/environment.py +++ b/src/ethereum/spurious_dragon/vm/instructions/environment.py @@ -334,8 +334,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -369,6 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/spurious_dragon/vm/instructions/system.py b/src/ethereum/spurious_dragon/vm/instructions/system.py index 98e5cc0c66..3ce0780272 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/system.py +++ b/src/ethereum/spurious_dragon/vm/instructions/system.py @@ -249,6 +249,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + create_gas_cost = ( Uint(0) if value == 0 or is_account_alive(evm.env.state, to) @@ -279,7 +282,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, memory_input_start_position, memory_input_size, diff --git a/src/ethereum/spurious_dragon/vm/interpreter.py b/src/ethereum/spurious_dragon/vm/interpreter.py index f31e77ea07..17d8f57f11 100644 --- a/src/ethereum/spurious_dragon/vm/interpreter.py +++ b/src/ethereum/spurious_dragon/vm/interpreter.py @@ -106,6 +106,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -125,12 +126,11 @@ def process_message_call( logs: Tuple[Log, ...] = () accounts_to_delete = set() touched_accounts = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete touched_accounts = evm.touched_accounts - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/spurious_dragon/vm/precompiled_contracts/ecrecover.py b/src/ethereum/spurious_dragon/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/spurious_dragon/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/spurious_dragon/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index 1419c87b6d..cf39821f14 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -36,12 +36,7 @@ set_account_balance, state_root, ) -from .transactions import ( - Transaction, - calculate_intrinsic_cost, - recover_sender, - validate_transaction, -) +from .transactions import Transaction, recover_sender, validate_transaction from .trie import Trie, root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -647,8 +642,7 @@ def process_transaction( logs : `Tuple[ethereum.blocks.Log, ...]` Logs generated during execution. """ - if not validate_transaction(tx): - raise InvalidBlock + intrinsic_gas = validate_transaction(tx) sender = env.origin sender_account = get_account(env.state, sender) @@ -660,7 +654,7 @@ def process_transaction( if sender_account.code != bytearray(): raise InvalidSenderError("not EOA") - gas = tx.gas - calculate_intrinsic_cost(tx) + gas = tx.gas - intrinsic_gas increment_nonce(env.state, sender) sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index 57d697d615..96d825e2ba 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError +from ethereum.exceptions import InvalidBlock, InvalidSignatureError from .fork_types import Address @@ -41,7 +41,7 @@ class Transaction: s: U256 -def validate_transaction(tx: Transaction) -> bool: +def validate_transaction(tx: Transaction) -> Uint: """ Verifies a transaction. @@ -63,14 +63,20 @@ def validate_transaction(tx: Transaction) -> bool: Returns ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True + intrinsic_gas = calculate_intrinsic_cost(tx) + if intrinsic_gas > tx.gas: + raise InvalidBlock + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidBlock + return intrinsic_gas def calculate_intrinsic_cost(tx: Transaction) -> Uint: diff --git a/src/ethereum/tangerine_whistle/vm/instructions/environment.py b/src/ethereum/tangerine_whistle/vm/instructions/environment.py index 9d936e7f5f..1bce21fda7 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/environment.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/environment.py @@ -334,8 +334,9 @@ def extcodesize(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - codesize = U256(len(get_account(evm.env.state, address).code)) + code = get_account(evm.env.state, address).code + codesize = U256(len(code)) push(evm.stack, codesize) # PROGRAM COUNTER @@ -369,6 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by code = get_account(evm.env.state, address).code + value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/tangerine_whistle/vm/instructions/system.py b/src/ethereum/tangerine_whistle/vm/instructions/system.py index 35d9cecdeb..b931d8e8eb 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/system.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/system.py @@ -248,6 +248,9 @@ def call(evm: Evm) -> None: (memory_output_start_position, memory_output_size), ], ) + + code_address = to + _account_exists = account_exists(evm.env.state, to) create_gas_cost = Uint(0) if _account_exists else GAS_NEW_ACCOUNT transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -275,7 +278,7 @@ def call(evm: Evm) -> None: value, evm.message.current_target, to, - to, + code_address, True, memory_input_start_position, memory_input_size, diff --git a/src/ethereum/tangerine_whistle/vm/interpreter.py b/src/ethereum/tangerine_whistle/vm/interpreter.py index c1f4296a3c..f7b487a150 100644 --- a/src/ethereum/tangerine_whistle/vm/interpreter.py +++ b/src/ethereum/tangerine_whistle/vm/interpreter.py @@ -100,6 +100,7 @@ def process_message_call( output : `MessageCallOutput` Output of the message call """ + refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( env.state, message.current_target @@ -116,11 +117,10 @@ def process_message_call( if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter = U256(evm.refund_counter) + refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error diff --git a/src/ethereum/tangerine_whistle/vm/precompiled_contracts/ecrecover.py b/src/ethereum/tangerine_whistle/vm/precompiled_contracts/ecrecover.py index 293e977575..1f047d3a44 100644 --- a/src/ethereum/tangerine_whistle/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/tangerine_whistle/vm/precompiled_contracts/ecrecover.py @@ -15,6 +15,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError from ethereum.utils.byte import left_pad_zero_bytes from ...vm import Evm @@ -53,7 +54,7 @@ def ecrecover(evm: Evm) -> None: try: public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except ValueError: + except InvalidSignatureError: # unable to extract public key return diff --git a/tests/berlin/test_transaction.py b/tests/berlin/test_transaction.py index 9c86d1643b..280530d07a 100644 --- a/tests/berlin/test_transaction.py +++ b/tests/berlin/test_transaction.py @@ -3,8 +3,11 @@ import pytest from ethereum_rlp import rlp -from ethereum.berlin.fork import calculate_intrinsic_cost, validate_transaction -from ethereum.berlin.transactions import LegacyTransaction +from ethereum.berlin.transactions import ( + LegacyTransaction, + validate_transaction, +) +from ethereum.exceptions import InvalidBlock from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -30,7 +33,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(LegacyTransaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -49,5 +53,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/byzantium/test_transaction.py b/tests/byzantium/test_transaction.py index a133df527d..a9003bac9e 100644 --- a/tests/byzantium/test_transaction.py +++ b/tests/byzantium/test_transaction.py @@ -3,11 +3,8 @@ import pytest from ethereum_rlp import rlp -from ethereum.byzantium.fork import ( - calculate_intrinsic_cost, - validate_transaction, -) -from ethereum.byzantium.transactions import Transaction +from ethereum.byzantium.transactions import Transaction, validate_transaction +from ethereum.exceptions import InvalidBlock from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +32,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -54,5 +52,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/constantinople/test_transaction.py b/tests/constantinople/test_transaction.py index 0916fedcbd..367bf2e4b3 100644 --- a/tests/constantinople/test_transaction.py +++ b/tests/constantinople/test_transaction.py @@ -3,11 +3,11 @@ import pytest from ethereum_rlp import rlp -from ethereum.constantinople.fork import ( - calculate_intrinsic_cost, +from ethereum.constantinople.transactions import ( + Transaction, validate_transaction, ) -from ethereum.constantinople.transactions import Transaction +from ethereum.exceptions import InvalidBlock from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +35,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -54,5 +55,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/frontier/test_transaction.py b/tests/frontier/test_transaction.py index 6b0c12f2e8..dff4ab44bb 100644 --- a/tests/frontier/test_transaction.py +++ b/tests/frontier/test_transaction.py @@ -3,11 +3,8 @@ import pytest from ethereum_rlp import rlp -from ethereum.frontier.fork import ( - calculate_intrinsic_cost, - validate_transaction, -) -from ethereum.frontier.transactions import Transaction +from ethereum.exceptions import InvalidBlock +from ethereum.frontier.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -33,7 +30,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -52,5 +50,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/homestead/test_transaction.py b/tests/homestead/test_transaction.py index 5fff1c1bbc..3f30927fed 100644 --- a/tests/homestead/test_transaction.py +++ b/tests/homestead/test_transaction.py @@ -3,11 +3,8 @@ import pytest from ethereum_rlp import rlp -from ethereum.homestead.fork import ( - calculate_intrinsic_cost, - validate_transaction, -) -from ethereum.homestead.transactions import Transaction +from ethereum.exceptions import InvalidBlock +from ethereum.homestead.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +32,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -54,5 +52,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/istanbul/test_transaction.py b/tests/istanbul/test_transaction.py index 6d0719fc3c..ec46c371b8 100644 --- a/tests/istanbul/test_transaction.py +++ b/tests/istanbul/test_transaction.py @@ -3,11 +3,8 @@ import pytest from ethereum_rlp import rlp -from ethereum.istanbul.fork import ( - calculate_intrinsic_cost, - validate_transaction, -) -from ethereum.istanbul.transactions import Transaction +from ethereum.exceptions import InvalidBlock +from ethereum.istanbul.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -33,7 +30,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -52,5 +50,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/london/test_transaction.py b/tests/london/test_transaction.py index 21b418d62c..760ad3351b 100644 --- a/tests/london/test_transaction.py +++ b/tests/london/test_transaction.py @@ -3,8 +3,11 @@ import pytest from ethereum_rlp import rlp -from ethereum.london.fork import calculate_intrinsic_cost, validate_transaction -from ethereum.london.transactions import LegacyTransaction +from ethereum.exceptions import InvalidBlock +from ethereum.london.transactions import ( + LegacyTransaction, + validate_transaction, +) from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -30,7 +33,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(LegacyTransaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -49,5 +53,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/spurious_dragon/test_transaction.py b/tests/spurious_dragon/test_transaction.py index fc70c08d14..ad68dafd68 100644 --- a/tests/spurious_dragon/test_transaction.py +++ b/tests/spurious_dragon/test_transaction.py @@ -3,11 +3,11 @@ import pytest from ethereum_rlp import rlp -from ethereum.spurious_dragon.fork import ( - calculate_intrinsic_cost, +from ethereum.exceptions import InvalidBlock +from ethereum.spurious_dragon.transactions import ( + Transaction, validate_transaction, ) -from ethereum.spurious_dragon.transactions import Transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +35,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -54,5 +55,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tests/tangerine_whistle/test_transaction.py b/tests/tangerine_whistle/test_transaction.py index 7e3a253fdb..3d70cff571 100644 --- a/tests/tangerine_whistle/test_transaction.py +++ b/tests/tangerine_whistle/test_transaction.py @@ -3,11 +3,11 @@ import pytest from ethereum_rlp import rlp -from ethereum.tangerine_whistle.fork import ( - calculate_intrinsic_cost, +from ethereum.exceptions import InvalidBlock +from ethereum.tangerine_whistle.transactions import ( + Transaction, validate_transaction, ) -from ethereum.tangerine_whistle.transactions import Transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +35,8 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - assert not validate_transaction(tx) + with pytest.raises(InvalidBlock): + validate_transaction(tx) @pytest.mark.parametrize( @@ -54,5 +55,5 @@ def test_nonce(test_file_nonce: str) -> None: test["test_result"]["intrinsicGas"] ) - assert validate_transaction(tx) - assert calculate_intrinsic_cost(tx) == result_intrinsic_gas_cost + intrinsic_gas = validate_transaction(tx) + assert intrinsic_gas == result_intrinsic_gas_cost diff --git a/tox.ini b/tox.ini index 7df4d42969..647714587b 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,6 @@ commands = pytest \ -m "not slow" \ -n auto --maxprocesses 5 \ - tests/prague \ --cov=ethereum --cov-report=term --cov-report "xml:{toxworkdir}/coverage.xml" \ --ignore-glob='tests/fixtures/*' \ --basetemp="{temp_dir}/pytest" @@ -39,7 +38,6 @@ commands = --disable-warnings \ -m "not slow" \ -n auto --maxprocesses 2 \ - tests/prague \ --ignore-glob='tests/fixtures/*' \ --basetemp="{temp_dir}/pytest" @@ -53,7 +51,6 @@ commands = pytest \ -m "not slow and not evm_tools" \ -n auto --maxprocesses 5 \ - tests/prague \ --ignore-glob='tests/fixtures/*' \ --ignore-glob='tests/test_t8n.py' \ --basetemp="{temp_dir}/pytest" \ From eb8c3a72e9c414e2ea50cb38b00be7e076bff14e Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 11 Feb 2025 18:39:09 -0500 Subject: [PATCH 23/70] Prepare to release --- setup.cfg | 12 +++++++++--- src/ethereum/__init__.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0d98cf55f8..0ec3656199 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] -name = ethereum -description = Ethereum specification, provided as a Python package for tooling and testing +name = ethereum-execution +description = Ethereum execution layer specification, provided as a Python package for tooling and testing long_description = file: README.md long_description_content_type = text/markdown version = attr: ethereum.__version__ @@ -9,6 +9,12 @@ license_files = LICENSE.md classifiers = License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: Implementation :: PyPy + Programming Language :: Python :: Implementation :: CPython + Intended Audience :: Developers + Natural Language :: English [options] packages = @@ -120,7 +126,7 @@ install_requires = pycryptodome>=3,<4 coincurve>=20,<21 typing_extensions>=4.2 - py-ecc >= 8.0.0b2, < 9 + py-ecc>=8.0.0b2,<9 ethereum-types>=0.2.1,<0.3 ethereum-rlp>=0.1.1,<0.2 diff --git a/src/ethereum/__init__.py b/src/ethereum/__init__.py index f7eeebb47c..8b5958b584 100644 --- a/src/ethereum/__init__.py +++ b/src/ethereum/__init__.py @@ -18,7 +18,7 @@ """ import sys -__version__ = "0.1.0" +__version__ = "1.17.0rc6.dev1" # # Ensure we can reach 1024 frames of recursion From 0d5af71827320ecddb37d9e6e86cbf872fe746f3 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 12 Feb 2025 19:50:22 -0500 Subject: [PATCH 24/70] Remove unused file --- src/ethereum/utils/safe_arithmetic.py | 95 --------------------------- 1 file changed, 95 deletions(-) delete mode 100644 src/ethereum/utils/safe_arithmetic.py diff --git a/src/ethereum/utils/safe_arithmetic.py b/src/ethereum/utils/safe_arithmetic.py deleted file mode 100644 index 0c1bfbe32e..0000000000 --- a/src/ethereum/utils/safe_arithmetic.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Safe Arithmetic for U256 Integer Type -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Safe arithmetic utility functions for U256 integer type. -""" -from typing import Optional, Type, Union - -from ethereum_types.numeric import U256, Uint - - -def u256_safe_add( - *numbers: Union[U256, Uint], - exception_type: Optional[Type[BaseException]] = None -) -> U256: - """ - Adds together the given sequence of numbers. If the total sum of the - numbers exceeds `U256.MAX_VALUE` then an exception is raised. - If `exception_type` = None then the exception raised defaults to the one - raised by `U256` when `U256.value > U256.MAX_VALUE` - else `exception_type` is raised. - - Parameters - ---------- - numbers : - The sequence of numbers that need to be added together. - - exception_type: - The exception that needs to be raised if the sum of the `numbers` - exceeds `U256.MAX_VALUE`. - - Returns - ------- - result : `ethereum.base_types.U256` - The sum of the given sequence of numbers if the total is less than - `U256.MAX_VALUE` else an exception is raised. - If `exception_type` = None then the exception raised defaults to the - one raised by `U256` when `U256.value > U256.MAX_VALUE` - else `exception_type` is raised. - """ - try: - return U256(sum(int(n) for n in numbers)) - except ValueError as e: - if exception_type: - raise exception_type from e - else: - raise e - - -def u256_safe_multiply( - *numbers: Union[U256, Uint], - exception_type: Optional[Type[BaseException]] = None -) -> U256: - """ - Multiplies together the given sequence of numbers. If the net product of - the numbers exceeds `U256.MAX_VALUE` then an exception is raised. - If `exception_type` = None then the exception raised defaults to the one - raised by `U256` when `U256.value > U256.MAX_VALUE` else - `exception_type` is raised. - - Parameters - ---------- - numbers : - The sequence of numbers that need to be multiplies together. - - exception_type: - The exception that needs to be raised if the sum of the `numbers` - exceeds `U256.MAX_VALUE`. - - Returns - ------- - result : `ethereum.base_types.U256` - The multiplication product of the given sequence of numbers if the - net product is less than `U256.MAX_VALUE` else an exception is raised. - If `exception_type` = None then the exception raised defaults to the - one raised by `U256` when `U256.value > U256.MAX_VALUE` - else `exception_type` is raised. - """ - result = Uint(numbers[0]) - try: - for number in numbers[1:]: - result *= Uint(number) - return U256(result) - except ValueError as e: - if exception_type: - raise exception_type from e - else: - raise e From 0d3aaa851b34c690d0e08bfb8fa9b2b1998446dd Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Thu, 13 Feb 2025 11:49:30 -0500 Subject: [PATCH 25/70] Bump ethash to 1.1.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0ec3656199..499c9c7720 100644 --- a/setup.cfg +++ b/setup.cfg @@ -191,7 +191,7 @@ doc = optimized = rust-pyspec-glue>=0.0.9,<0.1.0 - ethash @ git+https://github.com/chfast/ethash.git@e08bd0fadb8785f7ccf1e2fb07b75f54fe47f92e + ethash>=1.1.0,<2 [flake8] dictionaries=en_US,python,technical From 5f2d661dd30a5813aa972f27feb332735c6caaaa Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 12 Feb 2025 09:51:10 -0500 Subject: [PATCH 26/70] fix: check y_parity value (#1107) --- src/ethereum/prague/transactions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index f10f954fab..b79b9ab572 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -344,14 +344,20 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: signing_hash_155(tx, chain_id), ) elif isinstance(tx, AccessListTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") public_key = secp256k1_recover( r, s, tx.y_parity, signing_hash_2930(tx) ) elif isinstance(tx, FeeMarketTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") public_key = secp256k1_recover( r, s, tx.y_parity, signing_hash_1559(tx) ) elif isinstance(tx, BlobTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") public_key = secp256k1_recover( r, s, tx.y_parity, signing_hash_4844(tx) ) From 24de9248825b0d68ce58129a2d40449972bd2a4e Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Wed, 5 Mar 2025 07:38:13 +0100 Subject: [PATCH 27/70] Refactor Environment (#1131) * refactor blob gas logic There are parallels between how the regular block gas is handled and how the blob gas is handled. This commit refactors blob gas canculation to bring them in line with block gas * create BlockEnvironment and TransactionEnvironment Create the BlockEnvironment and TransactionEnvironment dataclasses BlockEnvironment holds data that is chain or block scoped TransactionEnvironment holds data that is transaction scoped Message holds data that is specific to a message call * refactor validate_header * update t8n * update evm tracing * backport changes to cancun * port to shanghai * port changes to paris * port to london * port berlin * port istanbul * port constantinople * port byzantium * port spurious_dragon * port older forks * fix t8n receipts * update testing * process_withdrawals update * fixes for doc * remove execution_spec_tests entry The tests from execution_spec_tests should now be a part of EEST * use default_factory in BlockOutput * minor fixes 1 * define current_block_number in block_hash opcode * rename tx_index to index_in_block * fix vm test runs --- src/ethereum/arrow_glacier/fork.py | 386 ++++------ src/ethereum/arrow_glacier/state.py | 19 +- src/ethereum/arrow_glacier/transactions.py | 45 +- src/ethereum/arrow_glacier/utils/message.py | 97 +-- src/ethereum/arrow_glacier/vm/__init__.py | 67 +- .../arrow_glacier/vm/instructions/block.py | 22 +- .../vm/instructions/environment.py | 18 +- .../arrow_glacier/vm/instructions/storage.py | 11 +- .../arrow_glacier/vm/instructions/system.py | 63 +- src/ethereum/arrow_glacier/vm/interpreter.py | 65 +- src/ethereum/berlin/fork.py | 361 ++++----- src/ethereum/berlin/state.py | 19 +- src/ethereum/berlin/transactions.py | 45 +- src/ethereum/berlin/utils/message.py | 97 +-- src/ethereum/berlin/vm/__init__.py | 67 +- src/ethereum/berlin/vm/instructions/block.py | 22 +- .../berlin/vm/instructions/environment.py | 16 +- .../berlin/vm/instructions/storage.py | 11 +- src/ethereum/berlin/vm/instructions/system.py | 64 +- src/ethereum/berlin/vm/interpreter.py | 61 +- src/ethereum/byzantium/fork.py | 349 ++++----- src/ethereum/byzantium/state.py | 19 +- src/ethereum/byzantium/transactions.py | 32 +- src/ethereum/byzantium/utils/message.py | 79 +- src/ethereum/byzantium/vm/__init__.py | 66 +- .../byzantium/vm/instructions/block.py | 20 +- .../byzantium/vm/instructions/environment.py | 11 +- .../byzantium/vm/instructions/storage.py | 9 +- .../byzantium/vm/instructions/system.py | 57 +- src/ethereum/byzantium/vm/interpreter.py | 59 +- src/ethereum/cancun/fork.py | 570 ++++++-------- src/ethereum/cancun/state.py | 15 - src/ethereum/cancun/transactions.py | 47 +- src/ethereum/cancun/utils/message.py | 97 +-- src/ethereum/cancun/vm/__init__.py | 80 +- src/ethereum/cancun/vm/instructions/block.py | 22 +- .../cancun/vm/instructions/environment.py | 26 +- .../cancun/vm/instructions/storage.py | 18 +- src/ethereum/cancun/vm/instructions/system.py | 51 +- src/ethereum/cancun/vm/interpreter.py | 63 +- src/ethereum/constantinople/fork.py | 349 ++++----- src/ethereum/constantinople/state.py | 19 +- src/ethereum/constantinople/transactions.py | 32 +- src/ethereum/constantinople/utils/message.py | 79 +- src/ethereum/constantinople/vm/__init__.py | 66 +- .../constantinople/vm/instructions/block.py | 20 +- .../vm/instructions/environment.py | 12 +- .../constantinople/vm/instructions/storage.py | 9 +- .../constantinople/vm/instructions/system.py | 60 +- src/ethereum/constantinople/vm/interpreter.py | 59 +- src/ethereum/dao_fork/fork.py | 334 ++++----- src/ethereum/dao_fork/transactions.py | 31 +- src/ethereum/dao_fork/utils/message.py | 73 +- src/ethereum/dao_fork/vm/__init__.py | 62 +- .../dao_fork/vm/instructions/block.py | 20 +- .../dao_fork/vm/instructions/environment.py | 11 +- .../dao_fork/vm/instructions/storage.py | 9 +- .../dao_fork/vm/instructions/system.py | 50 +- src/ethereum/dao_fork/vm/interpreter.py | 53 +- src/ethereum/frontier/fork.py | 333 ++++----- src/ethereum/frontier/transactions.py | 27 +- src/ethereum/frontier/utils/message.py | 68 +- src/ethereum/frontier/vm/__init__.py | 62 +- .../frontier/vm/instructions/block.py | 20 +- .../frontier/vm/instructions/environment.py | 11 +- .../frontier/vm/instructions/storage.py | 9 +- .../frontier/vm/instructions/system.py | 50 +- src/ethereum/frontier/vm/interpreter.py | 51 +- src/ethereum/gray_glacier/fork.py | 386 ++++------ src/ethereum/gray_glacier/state.py | 19 +- src/ethereum/gray_glacier/transactions.py | 45 +- src/ethereum/gray_glacier/utils/message.py | 97 +-- src/ethereum/gray_glacier/vm/__init__.py | 67 +- .../gray_glacier/vm/instructions/block.py | 22 +- .../vm/instructions/environment.py | 18 +- .../gray_glacier/vm/instructions/storage.py | 11 +- .../gray_glacier/vm/instructions/system.py | 63 +- src/ethereum/gray_glacier/vm/interpreter.py | 65 +- src/ethereum/homestead/fork.py | 333 ++++----- src/ethereum/homestead/transactions.py | 31 +- src/ethereum/homestead/utils/message.py | 73 +- src/ethereum/homestead/vm/__init__.py | 62 +- .../homestead/vm/instructions/block.py | 20 +- .../homestead/vm/instructions/environment.py | 11 +- .../homestead/vm/instructions/storage.py | 9 +- .../homestead/vm/instructions/system.py | 50 +- src/ethereum/homestead/vm/interpreter.py | 53 +- src/ethereum/istanbul/fork.py | 350 ++++----- src/ethereum/istanbul/state.py | 19 +- src/ethereum/istanbul/transactions.py | 32 +- src/ethereum/istanbul/utils/message.py | 79 +- src/ethereum/istanbul/vm/__init__.py | 65 +- .../istanbul/vm/instructions/block.py | 22 +- .../istanbul/vm/instructions/environment.py | 16 +- .../istanbul/vm/instructions/storage.py | 11 +- .../istanbul/vm/instructions/system.py | 60 +- src/ethereum/istanbul/vm/interpreter.py | 61 +- src/ethereum/london/fork.py | 386 ++++------ src/ethereum/london/state.py | 19 +- src/ethereum/london/transactions.py | 45 +- src/ethereum/london/utils/message.py | 97 +-- src/ethereum/london/vm/__init__.py | 67 +- src/ethereum/london/vm/instructions/block.py | 22 +- .../london/vm/instructions/environment.py | 18 +- .../london/vm/instructions/storage.py | 11 +- src/ethereum/london/vm/instructions/system.py | 63 +- src/ethereum/london/vm/interpreter.py | 61 +- src/ethereum/muir_glacier/fork.py | 348 ++++----- src/ethereum/muir_glacier/state.py | 19 +- src/ethereum/muir_glacier/transactions.py | 32 +- src/ethereum/muir_glacier/utils/message.py | 79 +- src/ethereum/muir_glacier/vm/__init__.py | 65 +- .../muir_glacier/vm/instructions/block.py | 22 +- .../vm/instructions/environment.py | 16 +- .../muir_glacier/vm/instructions/storage.py | 11 +- .../muir_glacier/vm/instructions/system.py | 60 +- src/ethereum/muir_glacier/vm/interpreter.py | 61 +- src/ethereum/paris/fork.py | 381 ++++------ src/ethereum/paris/state.py | 19 +- src/ethereum/paris/transactions.py | 45 +- src/ethereum/paris/utils/message.py | 97 +-- src/ethereum/paris/vm/__init__.py | 67 +- src/ethereum/paris/vm/instructions/block.py | 22 +- .../paris/vm/instructions/environment.py | 18 +- src/ethereum/paris/vm/instructions/storage.py | 11 +- src/ethereum/paris/vm/instructions/system.py | 63 +- src/ethereum/paris/vm/interpreter.py | 61 +- src/ethereum/prague/fork.py | 699 +++++++----------- src/ethereum/prague/state.py | 15 - src/ethereum/prague/transactions.py | 19 + src/ethereum/prague/utils/message.py | 103 +-- src/ethereum/prague/vm/__init__.py | 86 ++- src/ethereum/prague/vm/eoa_delegation.py | 30 +- src/ethereum/prague/vm/instructions/block.py | 22 +- .../prague/vm/instructions/environment.py | 26 +- .../prague/vm/instructions/storage.py | 18 +- src/ethereum/prague/vm/instructions/system.py | 53 +- src/ethereum/prague/vm/interpreter.py | 67 +- src/ethereum/shanghai/fork.py | 430 +++++------ src/ethereum/shanghai/state.py | 34 +- src/ethereum/shanghai/transactions.py | 47 +- src/ethereum/shanghai/utils/message.py | 97 +-- src/ethereum/shanghai/vm/__init__.py | 72 +- .../shanghai/vm/instructions/block.py | 22 +- .../shanghai/vm/instructions/environment.py | 18 +- .../shanghai/vm/instructions/storage.py | 11 +- .../shanghai/vm/instructions/system.py | 55 +- src/ethereum/shanghai/vm/interpreter.py | 61 +- src/ethereum/spurious_dragon/fork.py | 354 ++++----- src/ethereum/spurious_dragon/state.py | 19 +- src/ethereum/spurious_dragon/transactions.py | 32 +- src/ethereum/spurious_dragon/utils/message.py | 73 +- src/ethereum/spurious_dragon/vm/__init__.py | 66 +- .../spurious_dragon/vm/instructions/block.py | 20 +- .../vm/instructions/environment.py | 11 +- .../vm/instructions/storage.py | 9 +- .../spurious_dragon/vm/instructions/system.py | 58 +- .../spurious_dragon/vm/interpreter.py | 59 +- src/ethereum/tangerine_whistle/fork.py | 334 ++++----- .../tangerine_whistle/transactions.py | 31 +- .../tangerine_whistle/utils/message.py | 73 +- src/ethereum/tangerine_whistle/vm/__init__.py | 62 +- .../vm/instructions/block.py | 20 +- .../vm/instructions/environment.py | 11 +- .../vm/instructions/storage.py | 9 +- .../vm/instructions/system.py | 50 +- .../tangerine_whistle/vm/interpreter.py | 53 +- .../evm_tools/loaders/fork_loader.py | 117 +-- .../evm_tools/statetest/__init__.py | 2 +- .../evm_tools/t8n/__init__.py | 379 ++-------- .../evm_tools/t8n/evm_trace.py | 59 +- .../evm_tools/t8n/t8n_types.py | 132 ++-- tests/berlin/test_evm_tools.py | 48 +- tests/berlin/test_state_transition.py | 135 +--- tests/byzantium/test_evm_tools.py | 38 +- tests/byzantium/test_state_transition.py | 144 +--- tests/cancun/test_evm_tools.py | 46 +- tests/cancun/test_state_transition.py | 51 +- tests/conftest.py | 22 + tests/constantinople/test_evm_tools.py | 37 +- tests/constantinople/test_state_transition.py | 146 +--- tests/frontier/test_evm_tools.py | 38 +- tests/frontier/test_state_transition.py | 147 +--- tests/helpers/__init__.py | 10 +- tests/helpers/load_evm_tools_tests.py | 2 +- tests/helpers/load_vm_tests.py | 75 +- tests/homestead/test_evm_tools.py | 39 +- tests/homestead/test_state_transition.py | 145 +--- tests/istanbul/test_evm_tools.py | 48 +- tests/istanbul/test_state_transition.py | 135 +--- tests/london/test_evm_tools.py | 48 +- tests/london/test_state_transition.py | 137 +--- tests/paris/test_evm_tools.py | 48 +- tests/paris/test_state_transition.py | 137 +--- tests/prague/test_evm_tools.py | 50 +- tests/prague/test_state_transition.py | 65 +- tests/shanghai/test_evm_tools.py | 48 +- tests/shanghai/test_state_transition.py | 50 +- tests/spurious_dragon/test_evm_tools.py | 38 +- .../spurious_dragon/test_state_transition.py | 145 +--- tests/tangerine_whistle/test_evm_tools.py | 39 +- .../test_state_transition.py | 147 +--- whitelist.txt | 3 + 203 files changed, 7661 insertions(+), 8279 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index a01cb829b9..a6faa5429a 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -30,12 +30,13 @@ from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -48,10 +49,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -161,33 +163,42 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -259,7 +270,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -272,11 +283,27 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -388,24 +415,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - base_fee_per_gas: Uint, - gas_available: Uint, - chain_id: U64, ) -> Tuple[Address, Uint]: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - base_fee_per_gas : - The block base fee. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -419,25 +443,36 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, FeeMarketTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address, effective_gas_price @@ -478,46 +513,11 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -530,98 +530,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. - """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) + process_transaction(block_env, block_output, tx, Uint(i)) - sender_address, effective_gas_price = check_transaction( - tx, base_fee_per_gas, gas_available, chain_id - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -660,10 +589,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -745,8 +671,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -761,103 +690,116 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) - max_gas_fee: Uint - if isinstance(tx, FeeMarketTransaction): - max_gas_fee = Uint(tx.gas) * Uint(tx.max_fee_per_gas) - else: - max_gas_fee = Uint(tx.gas) * Uint(tx.gas_price) - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender_account = get_account(block_env.state, sender) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() + access_list_addresses = set() + access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/arrow_glacier/state.py b/src/ethereum/arrow_glacier/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/arrow_glacier/state.py +++ b/src/ethereum/arrow_glacier/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index 1e7680501b..d4a017576d 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -177,10 +177,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -191,15 +191,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -384,3 +384,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/arrow_glacier/utils/message.py b/src/ethereum/arrow_glacier/utils/message.py index c1268d4f6a..34461d7656 100644 --- a/src/ethereum/arrow_glacier/utils/message.py +++ b/src/ethereum/arrow_glacier/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this arrow_glacier version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/arrow_glacier/vm/__init__.py b/src/ethereum/arrow_glacier/vm/__init__.py index 6b4a3745d9..245a05e454 100644 --- a/src/ethereum/arrow_glacier/vm/__init__.py +++ b/src/ethereum/arrow_glacier/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,32 +22,74 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -57,6 +99,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -82,7 +126,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -114,7 +157,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -143,7 +186,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/arrow_glacier/vm/instructions/block.py b/src/ethereum/arrow_glacier/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/block.py +++ b/src/ethereum/arrow_glacier/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/arrow_glacier/vm/instructions/environment.py b/src/ethereum/arrow_glacier/vm/instructions/environment.py index dc59e168bd..172ce97d70 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/environment.py +++ b/src/ethereum/arrow_glacier/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -533,7 +535,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/arrow_glacier/vm/instructions/storage.py b/src/ethereum/arrow_glacier/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/storage.py +++ b/src/ethereum/arrow_glacier/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/arrow_glacier/vm/instructions/system.py b/src/ethereum/arrow_glacier/vm/instructions/system.py index 961b3e0c00..7a2f1efeb3 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/arrow_glacier/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + evm.accessed_addresses.add(contract_address) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) @@ -80,7 +84,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -92,19 +96,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -120,7 +124,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -157,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -273,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -290,7 +298,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -346,7 +354,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -362,7 +370,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -436,7 +444,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -481,8 +489,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -491,23 +502,29 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/arrow_glacier/vm/interpreter.py b/src/ethereum/arrow_glacier/vm/interpreter.py index 469e37d3a2..88aaf9dba2 100644 --- a/src/ethereum/arrow_glacier/vm/interpreter.py +++ b/src/ethereum/arrow_glacier/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -163,11 +161,12 @@ def process_create_message(message: Message, env: Environment) -> Evm: Returns ------- - evm: :py:class:`~ethereum.london.vm.Evm` + evm: :py:class:`~ethereum.arrow_glacier.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +175,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +195,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -221,33 +220,34 @@ def process_message(message: Message, env: Environment) -> Evm: Returns ------- - evm: :py:class:`~ethereum.london.vm.Evm` + evm: :py:class:`~ethereum.arrow_glacier.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +272,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 20cbb4ffc4..662974467f 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -11,7 +11,6 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union @@ -30,12 +29,13 @@ from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -47,10 +47,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -158,32 +159,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, ) - if apply_body_output.block_gas_used != block.header.gas_used: + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -193,7 +203,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -206,11 +216,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -311,21 +340,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -337,9 +366,20 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address @@ -380,45 +420,11 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -431,93 +437,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. - """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs + process_transaction(block_env, block_output, tx, Uint(i)) - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -556,10 +496,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -641,8 +578,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -657,87 +597,108 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() + access_list_addresses = set() + access_list_storage_keys = set() if isinstance(tx, AccessListTransaction): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/berlin/state.py b/src/ethereum/berlin/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/berlin/state.py +++ b/src/ethereum/berlin/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index b1b9f7ce74..355219eaa1 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -149,10 +149,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -163,15 +163,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, AccessListTransaction): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -318,3 +318,22 @@ def signing_hash_2930(tx: AccessListTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/berlin/utils/message.py b/src/ethereum/berlin/utils/message.py index 122d127a1c..caeecac1fc 100644 --- a/src/ethereum/berlin/utils/message.py +++ b/src/ethereum/berlin/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this berlin version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/berlin/vm/__init__.py b/src/ethereum/berlin/vm/__init__.py index 868b1fade5..76276e86fc 100644 --- a/src/ethereum/berlin/vm/__init__.py +++ b/src/ethereum/berlin/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,31 +22,73 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -56,6 +98,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -81,7 +125,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -113,7 +156,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -142,7 +185,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/berlin/vm/instructions/block.py b/src/ethereum/berlin/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/berlin/vm/instructions/block.py +++ b/src/ethereum/berlin/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/berlin/vm/instructions/environment.py b/src/ethereum/berlin/vm/instructions/environment.py index 0c2e50ab2c..a3807aa28b 100644 --- a/src/ethereum/berlin/vm/instructions/environment.py +++ b/src/ethereum/berlin/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) diff --git a/src/ethereum/berlin/vm/instructions/storage.py b/src/ethereum/berlin/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/berlin/vm/instructions/storage.py +++ b/src/ethereum/berlin/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/berlin/vm/instructions/system.py b/src/ethereum/berlin/vm/instructions/system.py index 5f1be5a398..a561423a0f 100644 --- a/src/ethereum/berlin/vm/instructions/system.py +++ b/src/ethereum/berlin/vm/instructions/system.py @@ -72,6 +72,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + evm.accessed_addresses.add(contract_address) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) @@ -81,7 +85,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -93,19 +97,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -121,7 +125,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -158,7 +162,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -274,8 +280,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -291,7 +299,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -347,7 +355,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -363,7 +371,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -437,7 +445,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -482,8 +490,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -502,23 +513,30 @@ def selfdestruct(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + originator = evm.message.current_target + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/berlin/vm/interpreter.py b/src/ethereum/berlin/vm/interpreter.py index b8b4971d21..7b675434d4 100644 --- a/src/ethereum/berlin/vm/interpreter.py +++ b/src/ethereum/berlin/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -88,9 +88,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -100,28 +98,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -149,7 +147,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -165,8 +163,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.berlin.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -175,15 +174,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -192,19 +191,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -220,30 +219,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.berlin.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -268,7 +268,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index 285da2ce1f..bd924561d1 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -11,12 +11,10 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass from typing import List, Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -30,19 +28,25 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -150,32 +154,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -185,7 +198,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -198,11 +211,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -303,21 +335,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -329,15 +361,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, error: Optional[EthereumException], cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -347,8 +389,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. error : Error in the top level frame of the transaction, if any. cumulative_gas_used : @@ -372,45 +412,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -423,90 +429,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs += logs + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - pay_rewards(state, block_number, coinbase, ommers) - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -545,10 +488,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -630,8 +570,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -646,77 +589,93 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/byzantium/state.py b/src/ethereum/byzantium/state.py index 85d5cc773b..e1928ac86f 100644 --- a/src/ethereum/byzantium/state.py +++ b/src/ethereum/byzantium/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -572,3 +572,20 @@ def increase_balance(account: Account) -> None: account.balance += amount modify_state(state, address, increase_balance) + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index 1c67eeaf02..21ffbc1b6f 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -157,6 +157,7 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) ) + return Address(keccak256(public_key)[12:32]) @@ -219,3 +220,18 @@ def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/byzantium/utils/message.py b/src/ethereum/byzantium/utils/message.py index 7e79326319..b00501e453 100644 --- a/src/ethereum/byzantium/utils/message.py +++ b/src/ethereum/byzantium/utils/message.py @@ -12,87 +12,68 @@ Message specific functions used in this byzantium version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.byzantium.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, parent_evm=None, ) diff --git a/src/ethereum/byzantium/vm/__init__.py b/src/ethereum/byzantium/vm/__init__.py index cba1349a74..81df1e3ad4 100644 --- a/src/ethereum/byzantium/vm/__init__.py +++ b/src/ethereum/byzantium/vm/__init__.py @@ -13,39 +13,80 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import Transaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -55,6 +96,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -78,7 +121,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -108,7 +150,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) @@ -135,7 +177,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/byzantium/vm/instructions/block.py b/src/ethereum/byzantium/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/byzantium/vm/instructions/block.py +++ b/src/ethereum/byzantium/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/byzantium/vm/instructions/environment.py b/src/ethereum/byzantium/vm/instructions/environment.py index 5a18a1c86d..561efd63da 100644 --- a/src/ethereum/byzantium/vm/instructions/environment.py +++ b/src/ethereum/byzantium/vm/instructions/environment.py @@ -75,7 +75,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -101,7 +101,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -312,7 +312,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -335,8 +335,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -371,7 +370,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/byzantium/vm/instructions/storage.py b/src/ethereum/byzantium/vm/instructions/storage.py index bb8596bbd7..bc1e9b5a2c 100644 --- a/src/ethereum/byzantium/vm/instructions/storage.py +++ b/src/ethereum/byzantium/vm/instructions/storage.py @@ -45,7 +45,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -68,7 +70,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -80,7 +83,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/byzantium/vm/instructions/system.py b/src/ethereum/byzantium/vm/instructions/system.py index 52388abb2b..827346bc97 100644 --- a/src/ethereum/byzantium/vm/instructions/system.py +++ b/src/ethereum/byzantium/vm/instructions/system.py @@ -83,11 +83,13 @@ def create(evm: Evm) -> None: evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -98,18 +100,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -123,7 +131,7 @@ def create(evm: Evm) -> None: is_static=False, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -201,8 +209,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -216,7 +226,7 @@ def generic_call( is_static=True if is_staticcall else evm.message.is_static, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -266,7 +276,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if value == 0 or is_account_alive(evm.env.state, to) + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -282,7 +292,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -349,7 +359,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -390,8 +400,11 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -410,23 +423,29 @@ def selfdestruct(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/byzantium/vm/interpreter.py b/src/ethereum/byzantium/vm/interpreter.py index fb99df6f05..8112dc3c18 100644 --- a/src/ethereum/byzantium/vm/interpreter.py +++ b/src/ethereum/byzantium/vm/interpreter.py @@ -48,7 +48,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -87,9 +87,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -99,28 +97,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -148,7 +146,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -164,8 +162,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.byzantium.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -173,10 +172,10 @@ def process_create_message(message: Message, env: Environment) -> Evm: # * The address created by two `CREATE` calls collide. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -185,19 +184,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -213,30 +212,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.byzantium.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -261,7 +261,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 3bbe82b69b..7932db33d3 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -16,7 +16,7 @@ from typing import List, Optional, Tuple, Union from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -29,7 +29,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root, VersionedHash +from .fork_types import Account, Address, VersionedHash from .state import ( State, TransientStorage, @@ -38,7 +38,7 @@ destroy_touched_empty_accounts, get_account, increment_nonce, - process_withdrawal, + modify_state, set_account_balance, state_root, ) @@ -50,10 +50,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.hexadecimal import hex_to_address from .utils.message import prepare_message from .vm import Message @@ -174,44 +175,50 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - excess_blob_gas = calculate_excess_blob_gas(parent_header) - if block.header.excess_blob_gas != excess_blob_gas: - raise InvalidBlock - - validate_header(block.header, parent_header) + validate_header(chain, block.header) if block.ommers != (): raise InvalidBlock - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.prev_randao, - block.transactions, - chain.chain_id, - block.withdrawals, - block.header.parent_beacon_block_root, - excess_blob_gas, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + excess_blob_gas=block.header.excess_blob_gas, + parent_beacon_block_root=block.header.parent_beacon_block_root, ) - if apply_body_output.block_gas_used != block.header.gas_used: + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + withdrawals=block.withdrawals, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + withdrawals_root = root(block_output.withdrawals_trie) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock - if apply_body_output.withdrawals_root != block.header.withdrawals_root: + if withdrawals_root != block.header.withdrawals_root: raise InvalidBlock - if apply_body_output.blob_gas_used != block.header.blob_gas_used: + if block_output.blob_gas_used != block.header.blob_gas_used: raise InvalidBlock chain.blocks.append(block) @@ -283,7 +290,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -296,11 +303,31 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + excess_blob_gas = calculate_excess_blob_gas(parent_header) + if header.excess_blob_gas != excess_blob_gas: + raise InvalidBlock + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -331,30 +358,21 @@ def validate_header(header: Header, parent_header: Header) -> None: def check_transaction( - state: State, + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, - base_fee_per_gas: Uint, - excess_blob_gas: U64, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...]]: +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: """ Check if the transaction is includable in the block. Parameters ---------- - state : - Current state. + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. - base_fee_per_gas : - The block base fee. - excess_blob_gas : - The excess blob gas. Returns ------- @@ -364,31 +382,41 @@ def check_transaction( The price to charge for gas when the transaction is executed. blob_versioned_hashes : The blob versioned hashes of the transaction. + tx_blob_gas_used: + The blob gas used by the transaction. Raises ------ InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used + blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used + if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) - sender_account = get_account(state, sender_address) + + tx_blob_gas_used = calculate_total_blob_gas(tx) + if tx_blob_gas_used > blob_gas_available: + raise InvalidBlock + + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price max_gas_fee = tx.gas * tx.gas_price @@ -402,7 +430,7 @@ def check_transaction( if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: raise InvalidBlock - blob_gas_price = calculate_blob_gas_price(excess_blob_gas) + blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: raise InvalidBlock @@ -416,10 +444,15 @@ def check_transaction( raise InvalidBlock if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): raise InvalidBlock - if sender_account.code != bytearray(): + if sender_account.code: raise InvalidSenderError("not EOA") - return sender_address, effective_gas_price, blob_versioned_hashes + return ( + sender_address, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) def make_receipt( @@ -458,91 +491,46 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - withdrawals_root : `ethereum.fork_types.Root` - Trie root of all the withdrawals in the block. - blob_gas_used : `ethereum.base_types.Uint` - Total blob gas used in the block. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - withdrawals_root: Root - blob_gas_used: Uint - - def process_system_transaction( + block_env: vm.BlockEnvironment, target_address: Address, data: Bytes, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, - state: State, - chain_id: U64, - excess_blob_gas: U64, ) -> MessageCallOutput: """ Process a system transaction. Parameters ---------- + block_env : + The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract. - block_hashes : - List of hashes of the previous 256 blocks. - coinbase : - Address of the block's coinbase. - block_number : - Block number. - base_fee_per_gas : - Base fee per gas. - block_gas_limit : - Gas limit of the block. - block_time : - Time the block was produced. - prev_randao : - Previous randao value. - state : - Current state. - chain_id : - ID of the chain. - excess_blob_gas : - Excess blob gas. Returns ------- system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = get_account(state, target_address).code + system_contract_code = get_account(block_env.state, target_address).code + + tx_env = vm.TransactionEnvironment( + origin=SYSTEM_ADDRESS, + gas_price=block_env.base_fee_per_gas, + gas=SYSTEM_TRANSACTION_GAS, + access_list_addresses=set(), + access_list_storage_keys=set(), + transient_storage=TransientStorage(), + blob_versioned_hashes=(), + index_in_block=None, + tx_hash=None, + traces=[], + ) system_tx_message = Message( + block_env=block_env, + tx_env=tx_env, caller=SYSTEM_ADDRESS, target=target_address, gas=SYSTEM_TRANSACTION_GAS, @@ -559,26 +547,7 @@ def process_system_transaction( parent_evm=None, ) - system_tx_env = vm.Environment( - caller=SYSTEM_ADDRESS, - block_hashes=block_hashes, - origin=SYSTEM_ADDRESS, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=base_fee_per_gas, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=(), - transient_storage=TransientStorage(), - ) - - system_tx_output = process_message_call(system_tx_message, system_tx_env) + system_tx_output = process_message_call(system_tx_message) # TODO: Empty accounts in post-merge forks are impossible # see Ethereum Improvement Proposal 7523. @@ -586,27 +555,17 @@ def process_system_transaction( # and will have to be removed in the future. # See https://github.com/ethereum/execution-specs/issues/955 destroy_touched_empty_accounts( - system_tx_env.state, system_tx_output.touched_accounts + block_env.state, system_tx_output.touched_accounts ) return system_tx_output def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - chain_id: U64, withdrawals: Tuple[Withdrawal, ...], - parent_beacon_block_root: Root, - excess_blob_gas: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -619,150 +578,40 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - prev_randao : - The previous randao from the beacon chain. + block_env : + The block scoped environment. transactions : Transactions included in the block. - ommers : - Headers of ancestor blocks which are not direct parents (formerly - uncles.) - chain_id : - ID of the executing chain. withdrawals : Withdrawals to be processed in the current block. - parent_beacon_block_root : - The root of the beacon block from the parent block. - excess_blob_gas : - Excess blob gas calculated from the previous block. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - blob_gas_used = Uint(0) - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() process_system_transaction( - BEACON_ROOTS_ADDRESS, - parent_beacon_block_root, - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - state, - chain_id, - excess_blob_gas, + block_env=block_env, + target_address=BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, ) for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) + process_transaction(block_env, block_output, tx, Uint(i)) - ( - sender_address, - effective_gas_price, - blob_versioned_hashes, - ) = check_transaction( - state, - tx, - gas_available, - chain_id, - base_fee_per_gas, - excess_blob_gas, - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=blob_versioned_hashes, - transient_storage=TransientStorage(), - ) + process_withdrawals(block_env, block_output, withdrawals) - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - blob_gas_used += calculate_total_blob_gas(tx) - if blob_gas_used > MAX_BLOB_GAS_PER_BLOCK: - raise InvalidBlock - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - for i, wd in enumerate(withdrawals): - trie_set(withdrawals_trie, rlp.encode(Uint(i)), rlp.encode(wd)) - - process_withdrawal(state, wd) - - if account_exists_and_is_empty(state, wd.address): - destroy_account(state, wd.address) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - root(withdrawals_trie), - blob_gas_used, - ) + return block_output def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -777,97 +626,154 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) if isinstance(tx, BlobTransaction): - blob_gas_fee = calculate_data_fee(env.excess_blob_gas, tx) + blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) else: blob_gas_fee = Uint(0) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() - preaccessed_addresses.add(env.coinbase) + access_list_addresses = set() + access_list_storage_keys = set() + access_list_addresses.add(block_env.coinbase) if isinstance( tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) ): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + transient_storage=TransientStorage(), + blob_versioned_hashes=blob_versioned_hashes, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) + + block_output.block_gas_used += tx_gas_used + block_output.blob_gas_used += tx_blob_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) + + block_output.block_logs += tx_output.logs + + +def process_withdrawals( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + withdrawals: Tuple[Withdrawal, ...], +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) - destroy_touched_empty_accounts(env.state, output.touched_accounts) + modify_state(block_env.state, wd.address, increase_recipient_balance) - return total_gas_used, output.logs, output.error + if account_exists_and_is_empty(block_env.state, wd.address): + destroy_account(block_env.state, wd.address) def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/cancun/state.py b/src/ethereum/cancun/state.py index 4b9c755a74..4f9d5ff4b2 100644 --- a/src/ethereum/cancun/state.py +++ b/src/ethereum/cancun/state.py @@ -24,7 +24,6 @@ from ethereum_types.frozen import modify from ethereum_types.numeric import U256, Uint -from .blocks import Withdrawal from .fork_types import EMPTY_ACCOUNT, Account, Address, Root from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set @@ -535,20 +534,6 @@ def increase_recipient_balance(recipient: Account) -> None: modify_state(state, recipient_address, increase_recipient_balance) -def process_withdrawal( - state: State, - wd: Withdrawal, -) -> None: - """ - Increase the balance of the withdrawing account. - """ - - def increase_recipient_balance(recipient: Account) -> None: - recipient.balance += wd.amount * U256(10**9) - - modify_state(state, wd.address, increase_recipient_balance) - - def set_account_balance(state: State, address: Address, amount: U256) -> None: """ Sets the balance of an account. diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index ce8a085f75..d4d8823f72 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -212,12 +212,12 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ from .vm.gas import init_code_cost - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -226,19 +226,19 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: data_cost += TX_DATA_COST_PER_NON_ZERO if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance( tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) ): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -463,3 +463,22 @@ def signing_hash_4844(tx: BlobTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/cancun/utils/message.py b/src/ethereum/cancun/utils/message.py index 68deb1a62f..37aff9a59a 100644 --- a/src/ethereum/cancun/utils/message.py +++ b/src/ethereum/cancun/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this cancun version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index b4cbf42ea2..be19e8081b 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,36 +22,87 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, VersionedHash from ..state import State, TransientStorage, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 prev_randao: Bytes32 - state: State - chain_id: U64 - traces: List[dict] excess_blob_gas: U64 - blob_versioned_hashes: Tuple[VersionedHash, ...] + parent_beacon_block_root: Hash32 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + withdrawals_trie : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + blob_gas_used : `ethereum.base_types.Uint` + Total blob gas used in the block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + blob_gas_used: Uint = Uint(0) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] transient_storage: TransientStorage + blob_versioned_hashes: Tuple[VersionedHash, ...] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] + traces: List[dict] @dataclass @@ -60,6 +111,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -85,7 +138,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -117,7 +169,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -146,7 +198,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/cancun/vm/instructions/block.py b/src/ethereum/cancun/vm/instructions/block.py index 2914c802c9..42c95480cf 100644 --- a/src/ethereum/cancun/vm/instructions/block.py +++ b/src/ethereum/cancun/vm/instructions/block.py @@ -44,13 +44,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -85,7 +91,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -118,7 +124,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -150,7 +156,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -182,7 +188,7 @@ def prev_randao(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.prev_randao)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -214,7 +220,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -243,7 +249,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/cancun/vm/instructions/environment.py b/src/ethereum/cancun/vm/instructions/environment.py index 6a524f1496..5ddd12dac8 100644 --- a/src/ethereum/cancun/vm/instructions/environment.py +++ b/src/ethereum/cancun/vm/instructions/environment.py @@ -85,7 +85,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -111,7 +111,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -322,7 +322,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -351,7 +351,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -393,7 +393,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -479,7 +479,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -511,7 +511,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -536,7 +538,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -559,8 +561,8 @@ def blob_hash(evm: Evm) -> None: charge_gas(evm, GAS_BLOBHASH_OPCODE) # OPERATION - if int(index) < len(evm.env.blob_versioned_hashes): - blob_hash = evm.env.blob_versioned_hashes[index] + if int(index) < len(evm.message.tx_env.blob_versioned_hashes): + blob_hash = evm.message.tx_env.blob_versioned_hashes[index] else: blob_hash = Bytes32(b"\x00" * 32) push(evm.stack, U256.from_be_bytes(blob_hash)) @@ -586,7 +588,9 @@ def blob_base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - blob_base_fee = calculate_blob_gas_price(evm.env.excess_blob_gas) + blob_base_fee = calculate_blob_gas_price( + evm.message.block_env.excess_blob_gas + ) push(evm.stack, U256(blob_base_fee)) # PROGRAM COUNTER diff --git a/src/ethereum/cancun/vm/instructions/storage.py b/src/ethereum/cancun/vm/instructions/storage.py index f88e295736..65a0d5a9b6 100644 --- a/src/ethereum/cancun/vm/instructions/storage.py +++ b/src/ethereum/cancun/vm/instructions/storage.py @@ -56,7 +56,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -80,10 +82,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -123,7 +126,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) @@ -146,7 +149,7 @@ def tload(evm: Evm) -> None: # OPERATION value = get_transient_storage( - evm.env.transient_storage, evm.message.current_target, key + evm.message.tx_env.transient_storage, evm.message.current_target, key ) push(evm.stack, value) @@ -171,7 +174,10 @@ def tstore(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext set_transient_storage( - evm.env.transient_storage, evm.message.current_target, key, new_value + evm.message.tx_env.transient_storage, + evm.message.current_target, + key, + new_value, ) # PROGRAM COUNTER diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index 58cec71f07..ff473fc285 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -93,7 +93,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -105,15 +105,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -129,7 +133,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -167,7 +171,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -297,8 +303,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -314,7 +322,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -370,7 +378,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -386,7 +394,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -460,7 +468,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -505,8 +513,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -515,10 +526,12 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account(evm.env.state, originator).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance move_ether( - evm.env.state, + evm.message.block_env.state, originator, beneficiary, originator_balance, @@ -526,14 +539,14 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion only if it was created # in the same transaction - if originator in evm.env.state.created_accounts: + if originator in evm.message.block_env.state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/cancun/vm/interpreter.py b/src/ethereum/cancun/vm/interpreter.py index 79a725b316..36a73f46b0 100644 --- a/src/ethereum/cancun/vm/interpreter.py +++ b/src/ethereum/cancun/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -166,8 +164,10 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.cancun.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage # take snapshot of state before processing the message - begin_transaction(env.state, env.transient_storage) + begin_transaction(state, transient_storage) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +176,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +196,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state, env.transient_storage) + set_code(state, message.current_target, contract_code) + commit_transaction(state, transient_storage) else: - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -224,30 +224,32 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.cancun.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state, env.transient_storage) + begin_transaction(state, transient_storage) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) else: - commit_transaction(env.state, env.transient_storage) + commit_transaction(state, transient_storage) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +274,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index e91d8565a7..02d1e3f75b 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -11,12 +11,10 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass from typing import List, Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -30,19 +28,25 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -150,32 +154,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -185,7 +198,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -198,11 +211,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -303,21 +335,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -329,15 +361,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, error: Optional[EthereumException], cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -347,8 +389,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. error : Error in the top level frame of the transaction, if any. cumulative_gas_used : @@ -372,45 +412,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -423,90 +429,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs += logs + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - pay_rewards(state, block_number, coinbase, ommers) - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -545,10 +488,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -630,8 +570,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -646,77 +589,93 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/constantinople/state.py b/src/ethereum/constantinople/state.py index 85d5cc773b..e1928ac86f 100644 --- a/src/ethereum/constantinople/state.py +++ b/src/ethereum/constantinople/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -572,3 +572,20 @@ def increase_balance(account: Account) -> None: account.balance += amount modify_state(state, address, increase_balance) + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index 1c67eeaf02..21ffbc1b6f 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -157,6 +157,7 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) ) + return Address(keccak256(public_key)[12:32]) @@ -219,3 +220,18 @@ def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/constantinople/utils/message.py b/src/ethereum/constantinople/utils/message.py index 147654e802..a47395dbf4 100644 --- a/src/ethereum/constantinople/utils/message.py +++ b/src/ethereum/constantinople/utils/message.py @@ -12,87 +12,68 @@ Message specific functions used in this constantinople version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.constantinople.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, parent_evm=None, ) diff --git a/src/ethereum/constantinople/vm/__init__.py b/src/ethereum/constantinople/vm/__init__.py index cba1349a74..81df1e3ad4 100644 --- a/src/ethereum/constantinople/vm/__init__.py +++ b/src/ethereum/constantinople/vm/__init__.py @@ -13,39 +13,80 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import Transaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -55,6 +96,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -78,7 +121,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -108,7 +150,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) @@ -135,7 +177,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/constantinople/vm/instructions/block.py b/src/ethereum/constantinople/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/constantinople/vm/instructions/block.py +++ b/src/ethereum/constantinople/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/constantinople/vm/instructions/environment.py b/src/ethereum/constantinople/vm/instructions/environment.py index af92af6891..77ebb8c5fb 100644 --- a/src/ethereum/constantinople/vm/instructions/environment.py +++ b/src/ethereum/constantinople/vm/instructions/environment.py @@ -78,7 +78,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -104,7 +104,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -315,7 +315,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -338,7 +338,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -373,7 +373,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -453,7 +453,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, GAS_CODE_HASH) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) diff --git a/src/ethereum/constantinople/vm/instructions/storage.py b/src/ethereum/constantinople/vm/instructions/storage.py index bb8596bbd7..bc1e9b5a2c 100644 --- a/src/ethereum/constantinople/vm/instructions/storage.py +++ b/src/ethereum/constantinople/vm/instructions/storage.py @@ -45,7 +45,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -68,7 +70,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -80,7 +83,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/constantinople/vm/instructions/system.py b/src/ethereum/constantinople/vm/instructions/system.py index 0c7608d40d..eaa8a98601 100644 --- a/src/ethereum/constantinople/vm/instructions/system.py +++ b/src/ethereum/constantinople/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -78,7 +82,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -90,9 +94,11 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return @@ -100,9 +106,11 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -116,7 +124,7 @@ def generic_create( is_static=False, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -153,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -269,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -284,7 +296,7 @@ def generic_call( is_static=True if is_staticcall else evm.message.is_static, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -334,7 +346,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if value == 0 or is_account_alive(evm.env.state, to) + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -350,7 +362,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -417,7 +429,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -458,8 +470,11 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -478,23 +493,30 @@ def selfdestruct(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + originator = evm.message.current_target + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/constantinople/vm/interpreter.py b/src/ethereum/constantinople/vm/interpreter.py index 80eeddc42e..2906c4161a 100644 --- a/src/ethereum/constantinople/vm/interpreter.py +++ b/src/ethereum/constantinople/vm/interpreter.py @@ -48,7 +48,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -87,9 +87,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -99,28 +97,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -148,7 +146,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -164,8 +162,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.constantinople.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -174,10 +173,10 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -186,19 +185,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -214,30 +213,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.constantinople.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -262,7 +262,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 7b653c5bc2..20bc645ef9 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -13,12 +13,11 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass -from typing import List, Optional, Set, Tuple +from typing import List, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -29,7 +28,7 @@ from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom from .dao import apply_dao -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, create_ether, @@ -39,8 +38,13 @@ set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -154,31 +158,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -188,7 +202,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -201,11 +215,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -311,18 +344,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. Returns ------- @@ -334,15 +370,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock sender_address = recover_sender(tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, post_state: Bytes32, cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -352,8 +398,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. post_state : The state root immediately after this transaction. cumulative_gas_used : @@ -377,44 +421,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -427,21 +438,8 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : @@ -450,65 +448,17 @@ def apply_body( Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, state_root(state), (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - pay_rewards(state, block_number, coinbase, ommers) + process_transaction(block_env, block_output, tx, Uint(i)) - block_gas_used = block_gas_limit - gas_available + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -547,10 +497,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -632,8 +579,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -648,70 +598,88 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee ) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + state_root(block_env.state), + block_output.block_gas_used, + tx_output.logs, + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index 96d825e2ba..6db00e736d 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(tx: Transaction) -> Address: @@ -180,3 +180,18 @@ def signing_hash(tx: Transaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/dao_fork/utils/message.py b/src/ethereum/dao_fork/utils/message.py index 43792fed6d..8ab3cd0ffa 100644 --- a/src/ethereum/dao_fork/utils/message.py +++ b/src/ethereum/dao_fork/utils/message.py @@ -11,82 +11,67 @@ Message specific functions used in this Dao Fork version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.dao_fork.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, + should_transfer_value=True, parent_evm=None, ) diff --git a/src/ethereum/dao_fork/vm/__init__.py b/src/ethereum/dao_fork/vm/__init__.py index 4cd35a43f0..d351223871 100644 --- a/src/ethereum/dao_fork/vm/__init__.py +++ b/src/ethereum/dao_fork/vm/__init__.py @@ -13,38 +13,79 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State +from ..transactions import Transaction +from ..trie import Trie __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -54,6 +95,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -76,7 +119,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int diff --git a/src/ethereum/dao_fork/vm/instructions/block.py b/src/ethereum/dao_fork/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/dao_fork/vm/instructions/block.py +++ b/src/ethereum/dao_fork/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/dao_fork/vm/instructions/environment.py b/src/ethereum/dao_fork/vm/instructions/environment.py index 1bce21fda7..36215ecb1a 100644 --- a/src/ethereum/dao_fork/vm/instructions/environment.py +++ b/src/ethereum/dao_fork/vm/instructions/environment.py @@ -73,7 +73,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -99,7 +99,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -310,7 +310,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -333,8 +333,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -369,7 +368,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/dao_fork/vm/instructions/storage.py b/src/ethereum/dao_fork/vm/instructions/storage.py index 7b299e07d0..76c11bcfe4 100644 --- a/src/ethereum/dao_fork/vm/instructions/storage.py +++ b/src/ethereum/dao_fork/vm/instructions/storage.py @@ -44,7 +44,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -67,7 +69,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -79,7 +82,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/dao_fork/vm/instructions/system.py b/src/ethereum/dao_fork/vm/instructions/system.py index 1e87e93319..4637a10c1d 100644 --- a/src/ethereum/dao_fork/vm/instructions/system.py +++ b/src/ethereum/dao_fork/vm/instructions/system.py @@ -73,11 +73,13 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -88,18 +90,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -112,7 +120,7 @@ def create(evm: Evm) -> None: should_transfer_value=True, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -185,8 +193,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -199,7 +209,7 @@ def generic_call( should_transfer_value=should_transfer_value, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -246,14 +256,14 @@ def call(evm: Evm) -> None: code_address = to message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -306,14 +316,14 @@ def callcode(evm: Evm) -> None: ], ) message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -366,17 +376,23 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) diff --git a/src/ethereum/dao_fork/vm/interpreter.py b/src/ethereum/dao_fork/vm/interpreter.py index 904c78f3cd..c92026b804 100644 --- a/src/ethereum/dao_fork/vm/interpreter.py +++ b/src/ethereum/dao_fork/vm/interpreter.py @@ -46,7 +46,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -80,9 +80,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -92,27 +90,25 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) + evm = process_message(message) if evm.error: logs: Tuple[Log, ...] = () @@ -136,7 +132,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -152,35 +148,36 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.dao_fork.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely # circumstances: # * The address created by two `CREATE` calls collide. # * The first `CREATE` left empty code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - evm = process_message(message, env) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT try: charge_gas(evm, contract_code_gas) except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -196,30 +193,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.dao_fork.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -244,7 +242,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 3e96a9aa73..3127970d36 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -13,10 +13,10 @@ """ from dataclasses import dataclass -from typing import List, Optional, Set, Tuple +from typing import List, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -26,7 +26,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, create_ether, @@ -36,8 +36,13 @@ set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -143,31 +148,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, ) - if apply_body_output.block_gas_used != block.header.gas_used: + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -177,7 +192,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -190,11 +205,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -293,18 +327,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. Returns ------- @@ -316,15 +353,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock sender_address = recover_sender(tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, post_state: Bytes32, cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -334,8 +381,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. post_state : The state root immediately after this transaction. cumulative_gas_used : @@ -359,44 +404,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -409,21 +421,8 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : @@ -432,65 +431,17 @@ def apply_body( Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, state_root(state), (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs + process_transaction(block_env, block_output, tx, Uint(i)) - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -529,10 +480,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -614,8 +562,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -630,70 +581,88 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee ) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + state_root(block_env.state), + block_output.block_gas_used, + tx_output.logs, + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index 396765e22c..e9801dfafe 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -17,9 +17,9 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) @slotted_freezable @@ -98,10 +98,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -109,7 +109,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: else: data_cost += TX_DATA_COST_PER_NON_ZERO - return Uint(TX_BASE_COST + data_cost) + return TX_BASE_COST + data_cost def recover_sender(tx: Transaction) -> Address: @@ -174,3 +174,18 @@ def signing_hash(tx: Transaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/frontier/utils/message.py b/src/ethereum/frontier/utils/message.py index d7bf7cc180..07af3bda16 100644 --- a/src/ethereum/frontier/utils/message.py +++ b/src/ethereum/frontier/utils/message.py @@ -11,74 +11,62 @@ Message specific functions used in this frontier version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.frontier.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), diff --git a/src/ethereum/frontier/vm/__init__.py b/src/ethereum/frontier/vm/__init__.py index 7d7b6e9ad4..5ea270ab77 100644 --- a/src/ethereum/frontier/vm/__init__.py +++ b/src/ethereum/frontier/vm/__init__.py @@ -13,38 +13,79 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State +from ..transactions import Transaction +from ..trie import Trie __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -54,6 +95,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -75,7 +118,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int diff --git a/src/ethereum/frontier/vm/instructions/block.py b/src/ethereum/frontier/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/frontier/vm/instructions/block.py +++ b/src/ethereum/frontier/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/frontier/vm/instructions/environment.py b/src/ethereum/frontier/vm/instructions/environment.py index 1bce21fda7..36215ecb1a 100644 --- a/src/ethereum/frontier/vm/instructions/environment.py +++ b/src/ethereum/frontier/vm/instructions/environment.py @@ -73,7 +73,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -99,7 +99,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -310,7 +310,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -333,8 +333,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -369,7 +368,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/frontier/vm/instructions/storage.py b/src/ethereum/frontier/vm/instructions/storage.py index 7b299e07d0..76c11bcfe4 100644 --- a/src/ethereum/frontier/vm/instructions/storage.py +++ b/src/ethereum/frontier/vm/instructions/storage.py @@ -44,7 +44,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -67,7 +69,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -79,7 +82,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/frontier/vm/instructions/system.py b/src/ethereum/frontier/vm/instructions/system.py index a86174de7e..0625f34a07 100644 --- a/src/ethereum/frontier/vm/instructions/system.py +++ b/src/ethereum/frontier/vm/instructions/system.py @@ -72,11 +72,13 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -87,18 +89,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -110,7 +118,7 @@ def create(evm: Evm) -> None: code_address=None, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -182,8 +190,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -195,7 +205,7 @@ def generic_call( code_address=code_address, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -242,14 +252,14 @@ def call(evm: Evm) -> None: code_address = to message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -301,14 +311,14 @@ def callcode(evm: Evm) -> None: ], ) message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -360,17 +370,23 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) diff --git a/src/ethereum/frontier/vm/interpreter.py b/src/ethereum/frontier/vm/interpreter.py index 490a91b75a..3d9cc64a31 100644 --- a/src/ethereum/frontier/vm/interpreter.py +++ b/src/ethereum/frontier/vm/interpreter.py @@ -46,7 +46,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -80,9 +80,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -92,27 +90,25 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) + evm = process_message(message) if evm.error: logs: Tuple[Log, ...] = () @@ -136,7 +132,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -152,17 +148,18 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.frontier.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely # circumstances: # * The address created by two `CREATE` calls collide. # * The first `CREATE` left empty code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - evm = process_message(message, env) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -171,14 +168,14 @@ def process_create_message(message: Message, env: Environment) -> Evm: except ExceptionalHalt: evm.output = b"" else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -194,30 +191,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.frontier.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -242,7 +240,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 9eadb2a99d..0dee77a920 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -30,12 +30,13 @@ from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -48,10 +49,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -161,33 +163,42 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -259,7 +270,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -272,11 +283,27 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -388,24 +415,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - base_fee_per_gas: Uint, - gas_available: Uint, - chain_id: U64, ) -> Tuple[Address, Uint]: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - base_fee_per_gas : - The block base fee. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -419,25 +443,36 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, FeeMarketTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address, effective_gas_price @@ -478,46 +513,11 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -530,98 +530,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. - """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) + process_transaction(block_env, block_output, tx, Uint(i)) - sender_address, effective_gas_price = check_transaction( - tx, base_fee_per_gas, gas_available, chain_id - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -660,10 +589,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -745,8 +671,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -761,103 +690,116 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) - max_gas_fee: Uint - if isinstance(tx, FeeMarketTransaction): - max_gas_fee = Uint(tx.gas) * Uint(tx.max_fee_per_gas) - else: - max_gas_fee = Uint(tx.gas) * Uint(tx.gas_price) - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender_account = get_account(block_env.state, sender) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() + access_list_addresses = set() + access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/gray_glacier/state.py b/src/ethereum/gray_glacier/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/gray_glacier/state.py +++ b/src/ethereum/gray_glacier/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index 1e7680501b..d4a017576d 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -177,10 +177,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -191,15 +191,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -384,3 +384,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/gray_glacier/utils/message.py b/src/ethereum/gray_glacier/utils/message.py index 73e4bc320f..ffd49e9790 100644 --- a/src/ethereum/gray_glacier/utils/message.py +++ b/src/ethereum/gray_glacier/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this gray_glacier version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/gray_glacier/vm/__init__.py b/src/ethereum/gray_glacier/vm/__init__.py index 6b4a3745d9..245a05e454 100644 --- a/src/ethereum/gray_glacier/vm/__init__.py +++ b/src/ethereum/gray_glacier/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,32 +22,74 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -57,6 +99,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -82,7 +126,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -114,7 +157,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -143,7 +186,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/gray_glacier/vm/instructions/block.py b/src/ethereum/gray_glacier/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/gray_glacier/vm/instructions/block.py +++ b/src/ethereum/gray_glacier/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/gray_glacier/vm/instructions/environment.py b/src/ethereum/gray_glacier/vm/instructions/environment.py index dc59e168bd..172ce97d70 100644 --- a/src/ethereum/gray_glacier/vm/instructions/environment.py +++ b/src/ethereum/gray_glacier/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -533,7 +535,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/gray_glacier/vm/instructions/storage.py b/src/ethereum/gray_glacier/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/gray_glacier/vm/instructions/storage.py +++ b/src/ethereum/gray_glacier/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/gray_glacier/vm/instructions/system.py b/src/ethereum/gray_glacier/vm/instructions/system.py index 961b3e0c00..7a2f1efeb3 100644 --- a/src/ethereum/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/gray_glacier/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + evm.accessed_addresses.add(contract_address) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) @@ -80,7 +84,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -92,19 +96,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -120,7 +124,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -157,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -273,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -290,7 +298,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -346,7 +354,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -362,7 +370,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -436,7 +444,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -481,8 +489,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -491,23 +502,29 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/gray_glacier/vm/interpreter.py b/src/ethereum/gray_glacier/vm/interpreter.py index 469e37d3a2..95f58ea19a 100644 --- a/src/ethereum/gray_glacier/vm/interpreter.py +++ b/src/ethereum/gray_glacier/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -163,11 +161,12 @@ def process_create_message(message: Message, env: Environment) -> Evm: Returns ------- - evm: :py:class:`~ethereum.london.vm.Evm` + evm: :py:class:`~ethereum.gray_glacier.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +175,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +195,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -221,33 +220,34 @@ def process_message(message: Message, env: Environment) -> Evm: Returns ------- - evm: :py:class:`~ethereum.london.vm.Evm` + evm: :py:class:`~ethereum.gray_glacier.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +272,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index cf39821f14..813f5766f0 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -13,10 +13,10 @@ """ from dataclasses import dataclass -from typing import List, Optional, Set, Tuple +from typing import List, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -26,7 +26,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, create_ether, @@ -36,8 +36,13 @@ set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -143,31 +148,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, ) - if apply_body_output.block_gas_used != block.header.gas_used: + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -177,7 +192,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -190,11 +205,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -293,18 +327,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. Returns ------- @@ -316,15 +353,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock sender_address = recover_sender(tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, post_state: Bytes32, cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -334,8 +381,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. post_state : The state root immediately after this transaction. cumulative_gas_used : @@ -359,44 +404,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -409,21 +421,8 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : @@ -432,65 +431,17 @@ def apply_body( Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, state_root(state), (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs + process_transaction(block_env, block_output, tx, Uint(i)) - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -529,10 +480,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -614,8 +562,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -630,70 +581,88 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee ) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + state_root(block_env.state), + block_output.block_gas_used, + tx_output.logs, + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index 96d825e2ba..6db00e736d 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(tx: Transaction) -> Address: @@ -180,3 +180,18 @@ def signing_hash(tx: Transaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/homestead/utils/message.py b/src/ethereum/homestead/utils/message.py index 436a35840b..8eb830f2d7 100644 --- a/src/ethereum/homestead/utils/message.py +++ b/src/ethereum/homestead/utils/message.py @@ -11,82 +11,67 @@ Message specific functions used in this homestead version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.homestead.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, + should_transfer_value=True, parent_evm=None, ) diff --git a/src/ethereum/homestead/vm/__init__.py b/src/ethereum/homestead/vm/__init__.py index 4cd35a43f0..d351223871 100644 --- a/src/ethereum/homestead/vm/__init__.py +++ b/src/ethereum/homestead/vm/__init__.py @@ -13,38 +13,79 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State +from ..transactions import Transaction +from ..trie import Trie __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -54,6 +95,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -76,7 +119,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int diff --git a/src/ethereum/homestead/vm/instructions/block.py b/src/ethereum/homestead/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/homestead/vm/instructions/block.py +++ b/src/ethereum/homestead/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/homestead/vm/instructions/environment.py b/src/ethereum/homestead/vm/instructions/environment.py index 1bce21fda7..36215ecb1a 100644 --- a/src/ethereum/homestead/vm/instructions/environment.py +++ b/src/ethereum/homestead/vm/instructions/environment.py @@ -73,7 +73,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -99,7 +99,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -310,7 +310,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -333,8 +333,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -369,7 +368,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/homestead/vm/instructions/storage.py b/src/ethereum/homestead/vm/instructions/storage.py index 7b299e07d0..76c11bcfe4 100644 --- a/src/ethereum/homestead/vm/instructions/storage.py +++ b/src/ethereum/homestead/vm/instructions/storage.py @@ -44,7 +44,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -67,7 +69,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -79,7 +82,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/homestead/vm/instructions/system.py b/src/ethereum/homestead/vm/instructions/system.py index 1e87e93319..4637a10c1d 100644 --- a/src/ethereum/homestead/vm/instructions/system.py +++ b/src/ethereum/homestead/vm/instructions/system.py @@ -73,11 +73,13 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -88,18 +90,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -112,7 +120,7 @@ def create(evm: Evm) -> None: should_transfer_value=True, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -185,8 +193,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -199,7 +209,7 @@ def generic_call( should_transfer_value=should_transfer_value, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -246,14 +256,14 @@ def call(evm: Evm) -> None: code_address = to message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -306,14 +316,14 @@ def callcode(evm: Evm) -> None: ], ) message_call_gas = calculate_message_call_gas( - evm.env.state, gas, to, value + evm.message.block_env.state, gas, to, value ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -366,17 +376,23 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) diff --git a/src/ethereum/homestead/vm/interpreter.py b/src/ethereum/homestead/vm/interpreter.py index 75462961fa..0ed6a8071c 100644 --- a/src/ethereum/homestead/vm/interpreter.py +++ b/src/ethereum/homestead/vm/interpreter.py @@ -46,7 +46,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -80,9 +80,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -92,27 +90,25 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) + evm = process_message(message) if evm.error: logs: Tuple[Log, ...] = () @@ -136,7 +132,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -152,35 +148,36 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.homestead.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely # circumstances: # * The address created by two `CREATE` calls collide. # * The first `CREATE` left empty code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - evm = process_message(message, env) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT try: charge_gas(evm, contract_code_gas) except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -196,30 +193,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.homestead.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -244,7 +242,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 7ecfa04c11..02d1e3f75b 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -11,12 +11,10 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass from typing import List, Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -30,19 +28,25 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -150,32 +154,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -185,7 +198,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -198,11 +211,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -303,21 +335,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -329,15 +361,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, error: Optional[EthereumException], cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -347,8 +389,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. error : Error in the top level frame of the transaction, if any. cumulative_gas_used : @@ -372,45 +412,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -423,91 +429,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs += logs + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - pay_rewards(state, block_number, coinbase, ommers) - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -546,10 +488,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -631,8 +570,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -647,77 +589,93 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/istanbul/state.py b/src/ethereum/istanbul/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/istanbul/state.py +++ b/src/ethereum/istanbul/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index e455e2549a..28115976e8 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -157,6 +157,7 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) ) + return Address(keccak256(public_key)[12:32]) @@ -219,3 +220,18 @@ def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/istanbul/utils/message.py b/src/ethereum/istanbul/utils/message.py index 0eeafed649..b84475427b 100644 --- a/src/ethereum/istanbul/utils/message.py +++ b/src/ethereum/istanbul/utils/message.py @@ -12,87 +12,68 @@ Message specific functions used in this istanbul version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.istanbul.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, parent_evm=None, ) diff --git a/src/ethereum/istanbul/vm/__init__.py b/src/ethereum/istanbul/vm/__init__.py index 75692435b9..81df1e3ad4 100644 --- a/src/ethereum/istanbul/vm/__init__.py +++ b/src/ethereum/istanbul/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 @@ -22,31 +22,71 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import Transaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -56,6 +96,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -79,7 +121,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -109,7 +150,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) @@ -136,7 +177,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/istanbul/vm/instructions/block.py b/src/ethereum/istanbul/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/istanbul/vm/instructions/block.py +++ b/src/ethereum/istanbul/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/istanbul/vm/instructions/environment.py b/src/ethereum/istanbul/vm/instructions/environment.py index fc300ca74a..9f26185b8f 100644 --- a/src/ethereum/istanbul/vm/instructions/environment.py +++ b/src/ethereum/istanbul/vm/instructions/environment.py @@ -79,7 +79,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -105,7 +105,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -316,7 +316,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -339,7 +339,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -374,7 +374,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -454,7 +454,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, GAS_CODE_HASH) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -486,7 +486,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) diff --git a/src/ethereum/istanbul/vm/instructions/storage.py b/src/ethereum/istanbul/vm/instructions/storage.py index b962c5fe4e..4bcc9aef2c 100644 --- a/src/ethereum/istanbul/vm/instructions/storage.py +++ b/src/ethereum/istanbul/vm/instructions/storage.py @@ -46,7 +46,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -70,10 +72,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) if original_value == current_value and current_value != new_value: if original_value == 0: @@ -105,7 +108,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/istanbul/vm/instructions/system.py b/src/ethereum/istanbul/vm/instructions/system.py index 0c7608d40d..eaa8a98601 100644 --- a/src/ethereum/istanbul/vm/instructions/system.py +++ b/src/ethereum/istanbul/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -78,7 +82,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -90,9 +94,11 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return @@ -100,9 +106,11 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -116,7 +124,7 @@ def generic_create( is_static=False, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -153,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -269,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -284,7 +296,7 @@ def generic_call( is_static=True if is_staticcall else evm.message.is_static, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -334,7 +346,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if value == 0 or is_account_alive(evm.env.state, to) + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -350,7 +362,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -417,7 +429,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -458,8 +470,11 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -478,23 +493,30 @@ def selfdestruct(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + originator = evm.message.current_target + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/istanbul/vm/interpreter.py b/src/ethereum/istanbul/vm/interpreter.py index 9eff74022b..45d2bfe3f0 100644 --- a/src/ethereum/istanbul/vm/interpreter.py +++ b/src/ethereum/istanbul/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -88,9 +88,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -100,28 +98,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -149,7 +147,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -165,8 +163,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.istanbul.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -175,15 +174,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -192,19 +191,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -220,30 +219,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.istanbul.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -268,7 +268,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index cb86d7f06b..fc8ea7adbc 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -30,12 +30,13 @@ from . import FORK_CRITERIA, vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -48,10 +49,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -162,33 +164,42 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -260,7 +271,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -273,11 +284,27 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -394,24 +421,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - base_fee_per_gas: Uint, - gas_available: Uint, - chain_id: U64, ) -> Tuple[Address, Uint]: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - base_fee_per_gas : - The block base fee. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -425,25 +449,36 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, FeeMarketTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address, effective_gas_price @@ -484,46 +519,11 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -536,98 +536,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. - """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) + process_transaction(block_env, block_output, tx, Uint(i)) - sender_address, effective_gas_price = check_transaction( - tx, base_fee_per_gas, gas_available, chain_id - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -666,10 +595,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -751,8 +677,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -767,103 +696,116 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) - max_gas_fee: Uint - if isinstance(tx, FeeMarketTransaction): - max_gas_fee = Uint(tx.gas) * Uint(tx.max_fee_per_gas) - else: - max_gas_fee = Uint(tx.gas) * Uint(tx.gas_price) - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender_account = get_account(block_env.state, sender) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() + access_list_addresses = set() + access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/london/state.py b/src/ethereum/london/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/london/state.py +++ b/src/ethereum/london/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index 1e7680501b..d4a017576d 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -177,10 +177,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -191,15 +191,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -384,3 +384,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/london/utils/message.py b/src/ethereum/london/utils/message.py index fcd8b1dc59..54caa37e91 100644 --- a/src/ethereum/london/utils/message.py +++ b/src/ethereum/london/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this london version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/london/vm/__init__.py b/src/ethereum/london/vm/__init__.py index 6b4a3745d9..245a05e454 100644 --- a/src/ethereum/london/vm/__init__.py +++ b/src/ethereum/london/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,32 +22,74 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -57,6 +99,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -82,7 +126,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -114,7 +157,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -143,7 +186,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/london/vm/instructions/block.py b/src/ethereum/london/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/london/vm/instructions/block.py +++ b/src/ethereum/london/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/london/vm/instructions/environment.py b/src/ethereum/london/vm/instructions/environment.py index dc59e168bd..172ce97d70 100644 --- a/src/ethereum/london/vm/instructions/environment.py +++ b/src/ethereum/london/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -533,7 +535,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/london/vm/instructions/storage.py b/src/ethereum/london/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/london/vm/instructions/storage.py +++ b/src/ethereum/london/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/london/vm/instructions/system.py b/src/ethereum/london/vm/instructions/system.py index 961b3e0c00..7a2f1efeb3 100644 --- a/src/ethereum/london/vm/instructions/system.py +++ b/src/ethereum/london/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + evm.accessed_addresses.add(contract_address) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) @@ -80,7 +84,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -92,19 +96,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -120,7 +124,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -157,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -273,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -290,7 +298,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -346,7 +354,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -362,7 +370,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -436,7 +444,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -481,8 +489,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -491,23 +502,29 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/london/vm/interpreter.py b/src/ethereum/london/vm/interpreter.py index 469e37d3a2..7ff7b55e29 100644 --- a/src/ethereum/london/vm/interpreter.py +++ b/src/ethereum/london/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -166,8 +164,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.london.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +175,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +195,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -224,30 +223,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.london.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +272,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index 423b250281..d8e65346dc 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -11,12 +11,10 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass from typing import List, Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -30,19 +28,25 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -150,32 +154,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -185,7 +198,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -198,11 +211,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -303,21 +335,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -329,15 +361,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, error: Optional[EthereumException], cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -372,45 +414,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -423,91 +431,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs += logs + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - pay_rewards(state, block_number, coinbase, ommers) - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -546,10 +490,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -631,8 +572,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -647,77 +591,93 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/muir_glacier/state.py b/src/ethereum/muir_glacier/state.py index 46ea928958..201d837261 100644 --- a/src/ethereum/muir_glacier/state.py +++ b/src/ethereum/muir_glacier/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -631,3 +631,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index e455e2549a..28115976e8 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -157,6 +157,7 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) ) + return Address(keccak256(public_key)[12:32]) @@ -219,3 +220,18 @@ def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/muir_glacier/utils/message.py b/src/ethereum/muir_glacier/utils/message.py index 2c7508621d..9d4c30605e 100644 --- a/src/ethereum/muir_glacier/utils/message.py +++ b/src/ethereum/muir_glacier/utils/message.py @@ -12,87 +12,68 @@ Message specific functions used in this muir_glacier version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.muir_glacier.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, parent_evm=None, ) diff --git a/src/ethereum/muir_glacier/vm/__init__.py b/src/ethereum/muir_glacier/vm/__init__.py index 75692435b9..81df1e3ad4 100644 --- a/src/ethereum/muir_glacier/vm/__init__.py +++ b/src/ethereum/muir_glacier/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 @@ -22,31 +22,71 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import Transaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -56,6 +96,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -79,7 +121,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -109,7 +150,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) @@ -136,7 +177,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/muir_glacier/vm/instructions/block.py b/src/ethereum/muir_glacier/vm/instructions/block.py index e94b8c69ed..2abe8928f2 100644 --- a/src/ethereum/muir_glacier/vm/instructions/block.py +++ b/src/ethereum/muir_glacier/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -201,7 +207,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/muir_glacier/vm/instructions/environment.py b/src/ethereum/muir_glacier/vm/instructions/environment.py index fc300ca74a..9f26185b8f 100644 --- a/src/ethereum/muir_glacier/vm/instructions/environment.py +++ b/src/ethereum/muir_glacier/vm/instructions/environment.py @@ -79,7 +79,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -105,7 +105,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -316,7 +316,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -339,7 +339,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -374,7 +374,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -454,7 +454,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, GAS_CODE_HASH) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -486,7 +486,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) diff --git a/src/ethereum/muir_glacier/vm/instructions/storage.py b/src/ethereum/muir_glacier/vm/instructions/storage.py index b962c5fe4e..4bcc9aef2c 100644 --- a/src/ethereum/muir_glacier/vm/instructions/storage.py +++ b/src/ethereum/muir_glacier/vm/instructions/storage.py @@ -46,7 +46,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -70,10 +72,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) if original_value == current_value and current_value != new_value: if original_value == 0: @@ -105,7 +108,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/muir_glacier/vm/instructions/system.py b/src/ethereum/muir_glacier/vm/instructions/system.py index 0c7608d40d..eaa8a98601 100644 --- a/src/ethereum/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/muir_glacier/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -78,7 +82,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -90,9 +94,11 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return @@ -100,9 +106,11 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -116,7 +124,7 @@ def generic_create( is_static=False, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -153,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -269,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -284,7 +296,7 @@ def generic_call( is_static=True if is_staticcall else evm.message.is_static, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -334,7 +346,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if value == 0 or is_account_alive(evm.env.state, to) + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -350,7 +362,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -417,7 +429,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -458,8 +470,11 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -478,23 +493,30 @@ def selfdestruct(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + originator = evm.message.current_target + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/muir_glacier/vm/interpreter.py b/src/ethereum/muir_glacier/vm/interpreter.py index c01f17c522..c860eb09d5 100644 --- a/src/ethereum/muir_glacier/vm/interpreter.py +++ b/src/ethereum/muir_glacier/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -88,9 +88,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -100,28 +98,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -149,7 +147,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -165,8 +163,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.muir_glacier.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -175,15 +174,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -192,19 +191,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -220,30 +219,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.muir_glacier.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -268,7 +268,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index c6b3f8538c..ac9a916675 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -16,7 +16,7 @@ from typing import List, Optional, Tuple, Union from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -29,11 +29,12 @@ from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -46,10 +47,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -155,33 +157,42 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) if block.ommers != (): raise InvalidBlock - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.prev_randao, - block.transactions, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -253,7 +264,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -266,11 +277,27 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -301,24 +328,21 @@ def validate_header(header: Header, parent_header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - base_fee_per_gas: Uint, - gas_available: Uint, - chain_id: U64, ) -> Tuple[Address, Uint]: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - base_fee_per_gas : - The block base fee. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -332,25 +356,36 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, FeeMarketTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address, effective_gas_price @@ -369,7 +404,7 @@ def make_receipt( tx : The executed transaction. error : - The error from the execution if any. + Error in the top level frame of the transaction, if any. cumulative_gas_used : The total gas used so far in the block after the transaction was executed. @@ -391,45 +426,10 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -442,101 +442,30 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - prev_randao : - The previous randao from the beacon chain. + block_env : + The block scoped environment. transactions : Transactions included in the block. - ommers : - Headers of ancestor blocks which are not direct parents (formerly - uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) - - sender_address, effective_gas_price = check_transaction( - tx, base_fee_per_gas, gas_available, chain_id - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used + process_transaction(block_env, block_output, tx, Uint(i)) - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -551,102 +480,116 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) - if isinstance(tx, FeeMarketTransaction): - max_gas_fee = tx.gas * tx.max_fee_per_gas - else: - max_gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender_account = get_account(block_env.state, sender) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() + access_list_addresses = set() + access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs, output.error + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/paris/state.py b/src/ethereum/paris/state.py index 7715eb3270..3650999343 100644 --- a/src/ethereum/paris/state.py +++ b/src/ethereum/paris/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -611,3 +611,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 1e7680501b..d4a017576d 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -177,10 +177,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -191,15 +191,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -384,3 +384,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/paris/utils/message.py b/src/ethereum/paris/utils/message.py index d0a74169b0..38554c73a8 100644 --- a/src/ethereum/paris/utils/message.py +++ b/src/ethereum/paris/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this paris version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/paris/vm/__init__.py b/src/ethereum/paris/vm/__init__.py index 920c234dd4..fe4d472966 100644 --- a/src/ethereum/paris/vm/__init__.py +++ b/src/ethereum/paris/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,32 +22,74 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 prev_randao: Bytes32 - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -57,6 +99,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -82,7 +126,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -114,7 +157,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -143,7 +186,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/paris/vm/instructions/block.py b/src/ethereum/paris/vm/instructions/block.py index 3e2b1aa3e8..f361a6ee39 100644 --- a/src/ethereum/paris/vm/instructions/block.py +++ b/src/ethereum/paris/vm/instructions/block.py @@ -44,13 +44,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -85,7 +91,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -118,7 +124,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -150,7 +156,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -182,7 +188,7 @@ def prev_randao(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.prev_randao)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -214,7 +220,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -243,7 +249,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/paris/vm/instructions/environment.py b/src/ethereum/paris/vm/instructions/environment.py index dc59e168bd..172ce97d70 100644 --- a/src/ethereum/paris/vm/instructions/environment.py +++ b/src/ethereum/paris/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -533,7 +535,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/paris/vm/instructions/storage.py b/src/ethereum/paris/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/paris/vm/instructions/storage.py +++ b/src/ethereum/paris/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index 961b3e0c00..7a2f1efeb3 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -71,6 +71,10 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + evm.accessed_addresses.add(contract_address) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) @@ -80,7 +84,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -92,19 +96,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -120,7 +124,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -157,7 +161,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -273,8 +279,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -290,7 +298,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -346,7 +354,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -362,7 +370,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -436,7 +444,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -481,8 +489,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -491,23 +502,29 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/paris/vm/interpreter.py b/src/ethereum/paris/vm/interpreter.py index cb2ec7c60e..fe3ae8d4cc 100644 --- a/src/ethereum/paris/vm/interpreter.py +++ b/src/ethereum/paris/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -166,8 +164,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.paris.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +175,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +195,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -224,30 +223,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.paris.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +272,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 9c149f65d7..50b5033e53 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -16,7 +16,7 @@ from typing import List, Optional, Tuple, Union from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -29,7 +29,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Authorization, Bloom, Root, VersionedHash +from .fork_types import Account, Address, Authorization, VersionedHash from .requests import ( CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, @@ -45,7 +45,7 @@ destroy_touched_empty_accounts, get_account, increment_nonce, - process_withdrawal, + modify_state, set_account_balance, state_root, ) @@ -58,10 +58,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.hexadecimal import hex_to_address from .utils.message import prepare_message from .vm import Message @@ -194,46 +195,53 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - excess_blob_gas = calculate_excess_blob_gas(parent_header) - if block.header.excess_blob_gas != excess_blob_gas: - raise InvalidBlock - - validate_header(block.header, parent_header) + validate_header(chain, block.header) if block.ommers != (): raise InvalidBlock - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.prev_randao, - block.transactions, - chain.chain_id, - block.withdrawals, - block.header.parent_beacon_block_root, - excess_blob_gas, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + excess_blob_gas=block.header.excess_blob_gas, + parent_beacon_block_root=block.header.parent_beacon_block_root, ) - if apply_body_output.block_gas_used != block.header.gas_used: + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + withdrawals=block.withdrawals, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + withdrawals_root = root(block_output.withdrawals_trie) + requests_hash = compute_requests_hash(block_output.requests) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock - if apply_body_output.withdrawals_root != block.header.withdrawals_root: + if withdrawals_root != block.header.withdrawals_root: raise InvalidBlock - if apply_body_output.blob_gas_used != block.header.blob_gas_used: + if block_output.blob_gas_used != block.header.blob_gas_used: raise InvalidBlock - if apply_body_output.requests_hash != block.header.requests_hash: + if requests_hash != block.header.requests_hash: raise InvalidBlock chain.blocks.append(block) @@ -305,7 +313,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -318,11 +326,31 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + excess_blob_gas = calculate_excess_blob_gas(parent_header) + if header.excess_blob_gas != excess_blob_gas: + raise InvalidBlock + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -353,30 +381,21 @@ def validate_header(header: Header, parent_header: Header) -> None: def check_transaction( - state: State, + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, - base_fee_per_gas: Uint, - excess_blob_gas: U64, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...]]: +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: """ Check if the transaction is includable in the block. Parameters ---------- - state : - Current state. + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. - base_fee_per_gas : - The block base fee. - excess_blob_gas : - The excess blob gas. Returns ------- @@ -386,33 +405,43 @@ def check_transaction( The price to charge for gas when the transaction is executed. blob_versioned_hashes : The blob versioned hashes of the transaction. + tx_blob_gas_used: + The blob gas used by the transaction. Raises ------ InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used + blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used + if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) - sender_account = get_account(state, sender_address) + + tx_blob_gas_used = calculate_total_blob_gas(tx) + if tx_blob_gas_used > blob_gas_available: + raise InvalidBlock + + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance( tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) ): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price max_gas_fee = tx.gas * tx.gas_price @@ -424,7 +453,7 @@ def check_transaction( if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: raise InvalidBlock - blob_gas_price = calculate_blob_gas_price(excess_blob_gas) + blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: raise InvalidBlock @@ -447,12 +476,15 @@ def check_transaction( raise InvalidBlock if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): raise InvalidBlock - if sender_account.code != bytearray() and not is_valid_delegation( - sender_account.code - ): + if sender_account.code and not is_valid_delegation(sender_account.code): raise InvalidSenderError("not EOA") - return sender_address, effective_gas_price, blob_versioned_hashes + return ( + sender_address, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) def make_receipt( @@ -491,94 +523,47 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - withdrawals_root : `ethereum.fork_types.Root` - Trie root of all the withdrawals in the block. - blob_gas_used : `ethereum.base_types.Uint` - Total blob gas used in the block. - requests_hash : `Bytes` - Hash of all the requests in the block. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - withdrawals_root: Root - blob_gas_used: Uint - requests_hash: Bytes - - def process_system_transaction( + block_env: vm.BlockEnvironment, target_address: Address, data: Bytes, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, - state: State, - chain_id: U64, - excess_blob_gas: U64, ) -> MessageCallOutput: """ Process a system transaction. Parameters ---------- + block_env : + The block scoped environment. target_address : Address of the contract to call. data : Data to pass to the contract. - block_hashes : - List of hashes of the previous 256 blocks. - coinbase : - Address of the block's coinbase. - block_number : - Block number. - base_fee_per_gas : - Base fee per gas. - block_gas_limit : - Gas limit of the block. - block_time : - Time the block was produced. - prev_randao : - Previous randao value. - state : - Current state. - chain_id : - ID of the chain. - excess_blob_gas : - Excess blob gas. Returns ------- system_tx_output : `MessageCallOutput` Output of processing the system transaction. """ - system_contract_code = get_account(state, target_address).code + system_contract_code = get_account(block_env.state, target_address).code + + tx_env = vm.TransactionEnvironment( + origin=SYSTEM_ADDRESS, + gas_price=block_env.base_fee_per_gas, + gas=SYSTEM_TRANSACTION_GAS, + access_list_addresses=set(), + access_list_storage_keys=set(), + transient_storage=TransientStorage(), + blob_versioned_hashes=(), + authorizations=(), + index_in_block=None, + tx_hash=None, + traces=[], + ) system_tx_message = Message( + block_env=block_env, + tx_env=tx_env, caller=SYSTEM_ADDRESS, target=target_address, gas=SYSTEM_TRANSACTION_GAS, @@ -593,29 +578,9 @@ def process_system_transaction( accessed_addresses=set(), accessed_storage_keys=set(), parent_evm=None, - authorizations=(), ) - system_tx_env = vm.Environment( - caller=SYSTEM_ADDRESS, - block_hashes=block_hashes, - origin=SYSTEM_ADDRESS, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=base_fee_per_gas, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=(), - transient_storage=TransientStorage(), - ) - - system_tx_output = process_message_call(system_tx_message, system_tx_env) + system_tx_output = process_message_call(system_tx_message) # TODO: Empty accounts in post-merge forks are impossible # see Ethereum Improvement Proposal 7523. @@ -623,27 +588,17 @@ def process_system_transaction( # and will have to be removed in the future. # See https://github.com/ethereum/execution-specs/issues/955 destroy_touched_empty_accounts( - system_tx_env.state, system_tx_output.touched_accounts + block_env.state, system_tx_output.touched_accounts ) return system_tx_output def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - chain_id: U64, withdrawals: Tuple[Withdrawal, ...], - parent_beacon_block_root: Root, - excess_blob_gas: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -656,246 +611,69 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - prev_randao : - The previous randao from the beacon chain. + block_env : + The block scoped environment. transactions : Transactions included in the block. - ommers : - Headers of ancestor blocks which are not direct parents (formerly - uncles.) - chain_id : - ID of the executing chain. withdrawals : Withdrawals to be processed in the current block. - parent_beacon_block_root : - The root of the beacon block from the parent block. - excess_blob_gas : - Excess blob gas calculated from the previous block. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - blob_gas_used = Uint(0) - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () - deposit_requests: Bytes = b"" + block_output = vm.BlockOutput() process_system_transaction( - BEACON_ROOTS_ADDRESS, - parent_beacon_block_root, - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - state, - chain_id, - excess_blob_gas, + block_env=block_env, + target_address=BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, ) process_system_transaction( - HISTORY_STORAGE_ADDRESS, - block_hashes[-1], # The parent hash - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - state, - chain_id, - excess_blob_gas, + block_env=block_env, + target_address=HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash ) for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) - - ( - sender_address, - effective_gas_price, - blob_versioned_hashes, - ) = check_transaction( - state, - tx, - gas_available, - chain_id, - base_fee_per_gas, - excess_blob_gas, - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - excess_blob_gas=excess_blob_gas, - blob_versioned_hashes=blob_versioned_hashes, - transient_storage=TransientStorage(), - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - deposit_requests += parse_deposit_requests_from_receipt(receipt) + process_withdrawals(block_env, block_output, withdrawals) - block_logs += logs - blob_gas_used += calculate_total_blob_gas(tx) - if blob_gas_used > MAX_BLOB_GAS_PER_BLOCK: - raise InvalidBlock - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - for i, wd in enumerate(withdrawals): - trie_set(withdrawals_trie, rlp.encode(Uint(i)), rlp.encode(wd)) - - process_withdrawal(state, wd) - - if account_exists_and_is_empty(state, wd.address): - destroy_account(state, wd.address) - - requests_from_execution = process_general_purpose_requests( - deposit_requests, - state, - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - chain_id, - excess_blob_gas, + process_general_purpose_requests( + block_env=block_env, + block_output=block_output, ) - requests_hash = compute_requests_hash(requests_from_execution) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - root(withdrawals_trie), - blob_gas_used, - requests_hash, - ) + return block_output def process_general_purpose_requests( - deposit_requests: Bytes, - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, - chain_id: U64, - excess_blob_gas: U64, -) -> List[Bytes]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, +) -> None: """ Process all the requests in the block. Parameters ---------- - deposit_requests : - The deposit requests. - state : - Current state. - block_hashes : - List of hashes of the previous 256 blocks. - coinbase : - Address of the block's coinbase. - block_number : - Block number. - base_fee_per_gas : - Base fee per gas. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced. - prev_randao : - The previous randao from the beacon chain. - chain_id : - ID of the executing chain. - excess_blob_gas : - Excess blob gas. - - Returns - ------- - requests_from_execution : `List[Bytes]` - The requests from the execution + block_env : + The execution environment for the Block. + block_output : + The block output for the current block. """ # Requests are to be in ascending order of request type - requests_from_execution: List[Bytes] = [] + deposit_requests = block_output.deposit_requests + requests_from_execution = block_output.requests if len(deposit_requests) > 0: requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) system_withdrawal_tx_output = process_system_transaction( - WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - b"", - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - state, - chain_id, - excess_blob_gas, + block_env=block_env, + target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + data=b"", ) if len(system_withdrawal_tx_output.return_data) > 0: @@ -904,18 +682,9 @@ def process_general_purpose_requests( ) system_consolidation_tx_output = process_system_transaction( - CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, - b"", - block_hashes, - coinbase, - block_number, - base_fee_per_gas, - block_gas_limit, - block_time, - prev_randao, - state, - chain_id, - excess_blob_gas, + block_env=block_env, + target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + data=b"", ) if len(system_consolidation_tx_output.return_data) > 0: @@ -924,12 +693,13 @@ def process_general_purpose_requests( + system_consolidation_tx_output.return_data ) - return requests_from_execution - def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -944,41 +714,56 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) if isinstance(tx, BlobTransaction): - blob_gas_fee = calculate_data_fee(env.excess_blob_gas, tx) + blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) else: blob_gas_fee = Uint(0) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() - preaccessed_addresses.add(env.coinbase) + access_list_addresses = set() + access_list_storage_keys = set() + access_list_addresses.add(block_env.coinbase) if isinstance( tx, ( @@ -989,70 +774,118 @@ def process_transaction( ), ): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) + access_list_storage_keys.add((address, key)) authorizations: Tuple[Authorization, ...] = () if isinstance(tx, SetCodeTransaction): authorizations = tx.authorizations - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + transient_storage=TransientStorage(), + blob_versioned_hashes=blob_versioned_hashes, authorizations=authorizations, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) # For EIP-7623 we first calculate the execution_gas_used, which includes # the execution gas refund. - execution_gas_used = tx.gas - output.gas_left + execution_gas_used = tx.gas - tx_output.gas_left gas_refund = min( - execution_gas_used // Uint(5), Uint(output.refund_counter) + execution_gas_used // Uint(5), Uint(tx_output.refund_counter) ) execution_gas_used -= gas_refund # Transactions with less execution_gas_used than the floor pay at the # floor cost. - total_gas_used = max(execution_gas_used, calldata_floor_gas_cost) + tx_gas_used = max(execution_gas_used, calldata_floor_gas_cost) - output.gas_left = tx.gas - total_gas_used - gas_refund_amount = output.gas_left * env.gas_price + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = total_gas_used * priority_fee_per_gas + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) + + block_output.block_gas_used += tx_gas_used + block_output.blob_gas_used += tx_blob_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + block_output.block_logs += tx_output.logs + + block_output.deposit_requests += parse_deposit_requests_from_receipt( + receipt + ) + + +def process_withdrawals( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + withdrawals: Tuple[Withdrawal, ...], +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) - destroy_touched_empty_accounts(env.state, output.touched_accounts) + modify_state(block_env.state, wd.address, increase_recipient_balance) - return total_gas_used, output.logs, output.error + if account_exists_and_is_empty(block_env.state, wd.address): + destroy_account(block_env.state, wd.address) def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py index 4b9c755a74..4f9d5ff4b2 100644 --- a/src/ethereum/prague/state.py +++ b/src/ethereum/prague/state.py @@ -24,7 +24,6 @@ from ethereum_types.frozen import modify from ethereum_types.numeric import U256, Uint -from .blocks import Withdrawal from .fork_types import EMPTY_ACCOUNT, Account, Address, Root from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set @@ -535,20 +534,6 @@ def increase_recipient_balance(recipient: Account) -> None: modify_state(state, recipient_address, increase_recipient_balance) -def process_withdrawal( - state: State, - wd: Withdrawal, -) -> None: - """ - Increase the balance of the withdrawing account. - """ - - def increase_recipient_balance(recipient: Account) -> None: - recipient.balance += wd.amount * U256(10**9) - - modify_state(state, wd.address, increase_recipient_balance) - - def set_account_balance(state: State, address: Address, amount: U256) -> None: """ Sets the balance of an account. diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index b79b9ab572..4bfc077b02 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -558,3 +558,22 @@ def signing_hash_7702(tx: SetCodeTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py index 3d1420b835..9d3f615739 100644 --- a/src/ethereum/prague/utils/message.py +++ b/src/ethereum/prague/utils/message.py @@ -12,68 +12,34 @@ Message specific functions used in this prague version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint - -from ..fork_types import Address, Authorization +from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.eoa_delegation import get_delegated_code_address from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), - authorizations: Tuple[Authorization, ...] = (), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call - authorizations: - Authorizations that should be applied to the message call. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -81,48 +47,49 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) + accessed_addresses.update(tx_env.access_list_addresses) - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code delegated_address = get_delegated_code_address(code) if delegated_address is not None: accessed_addresses.add(delegated_address) - code = get_account(env.state, delegated_address).code + code = get_account(block_env.state, delegated_address).code - if code_address is None: - code_address = target + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, - authorizations=authorizations, ) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index e2abe20d98..55f1b92499 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,36 +22,92 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, Authorization, VersionedHash from ..state import State, TransientStorage, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 prev_randao: Bytes32 - state: State - chain_id: U64 - traces: List[dict] excess_blob_gas: U64 - blob_versioned_hashes: Tuple[VersionedHash, ...] + parent_beacon_block_root: Hash32 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + withdrawals_trie : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + blob_gas_used : `ethereum.base_types.Uint` + Total blob gas used in the block. + requests : `Bytes` + Hash of all the requests in the block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + blob_gas_used: Uint = Uint(0) + deposit_requests: Bytes = Bytes(b"") + requests: List[Bytes] = field(default_factory=list) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] transient_storage: TransientStorage + blob_versioned_hashes: Tuple[VersionedHash, ...] + authorizations: Tuple[Authorization, ...] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] + traces: List[dict] @dataclass @@ -60,6 +116,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -74,7 +132,6 @@ class Message: accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] parent_evm: Optional["Evm"] - authorizations: Tuple[Authorization, ...] @dataclass @@ -86,7 +143,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -118,7 +174,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -147,7 +203,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/prague/vm/eoa_delegation.py b/src/ethereum/prague/vm/eoa_delegation.py index 8728375a25..bb6a7e33d9 100644 --- a/src/ethereum/prague/vm/eoa_delegation.py +++ b/src/ethereum/prague/vm/eoa_delegation.py @@ -17,7 +17,7 @@ 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 Environment, Evm, Message +from . import Evm, Message SET_CODE_TX_MAGIC = b"\x05" EOA_DELEGATION_MARKER = b"\xEF\x01\x00" @@ -130,7 +130,8 @@ def access_delegation( delegation : `Tuple[bool, Address, Bytes, Uint]` The delegation address, code, and access gas cost. """ - code = get_account(evm.env.state, address).code + state = evm.message.block_env.state + code = get_account(state, address).code if not is_valid_delegation(code): return False, address, code, Uint(0) @@ -140,12 +141,12 @@ def access_delegation( else: evm.accessed_addresses.add(address) access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - code = get_account(evm.env.state, address).code + code = get_account(state, address).code return True, address, code, access_gas_cost -def set_delegation(message: Message, env: Environment) -> U256: +def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -161,9 +162,10 @@ def set_delegation(message: Message, env: Environment) -> U256: refund_counter: `U256` Refund from authority which already exists in state. """ + state = message.block_env.state refund_counter = U256(0) - for auth in message.authorizations: - if auth.chain_id not in (env.chain_id, U256(0)): + for auth in message.tx_env.authorizations: + if auth.chain_id not in (message.block_env.chain_id, U256(0)): continue if auth.nonce >= U64.MAX_VALUE: @@ -176,32 +178,30 @@ def set_delegation(message: Message, env: Environment) -> U256: message.accessed_addresses.add(authority) - authority_account = get_account(env.state, authority) + authority_account = get_account(state, authority) authority_code = authority_account.code - if authority_code != bytearray() and not is_valid_delegation( - authority_code - ): + if authority_code and not is_valid_delegation(authority_code): continue authority_nonce = authority_account.nonce if authority_nonce != auth.nonce: continue - if account_exists(env.state, authority): + if account_exists(state, 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(env.state, authority, code_to_set) + set_code(state, authority, code_to_set) - increment_nonce(env.state, authority) + increment_nonce(state, authority) if message.code_address is None: raise InvalidBlock("Invalid type 4 transaction: no target") - message.code = get_account(env.state, message.code_address).code + message.code = get_account(state, message.code_address).code if is_valid_delegation(message.code): message.code_address = Address( @@ -209,6 +209,6 @@ def set_delegation(message: Message, env: Environment) -> U256: ) message.accessed_addresses.add(message.code_address) - message.code = get_account(env.state, message.code_address).code + message.code = get_account(state, message.code_address).code return refund_counter diff --git a/src/ethereum/prague/vm/instructions/block.py b/src/ethereum/prague/vm/instructions/block.py index b4f7b556d7..e47a99de85 100644 --- a/src/ethereum/prague/vm/instructions/block.py +++ b/src/ethereum/prague/vm/instructions/block.py @@ -44,13 +44,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -85,7 +91,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -118,7 +124,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -150,7 +156,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -182,7 +188,7 @@ def prev_randao(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.prev_randao)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -214,7 +220,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -243,7 +249,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/prague/vm/instructions/environment.py b/src/ethereum/prague/vm/instructions/environment.py index 6a524f1496..5ddd12dac8 100644 --- a/src/ethereum/prague/vm/instructions/environment.py +++ b/src/ethereum/prague/vm/instructions/environment.py @@ -85,7 +85,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -111,7 +111,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -322,7 +322,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -351,7 +351,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -393,7 +393,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -479,7 +479,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -511,7 +511,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -536,7 +538,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -559,8 +561,8 @@ def blob_hash(evm: Evm) -> None: charge_gas(evm, GAS_BLOBHASH_OPCODE) # OPERATION - if int(index) < len(evm.env.blob_versioned_hashes): - blob_hash = evm.env.blob_versioned_hashes[index] + if int(index) < len(evm.message.tx_env.blob_versioned_hashes): + blob_hash = evm.message.tx_env.blob_versioned_hashes[index] else: blob_hash = Bytes32(b"\x00" * 32) push(evm.stack, U256.from_be_bytes(blob_hash)) @@ -586,7 +588,9 @@ def blob_base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - blob_base_fee = calculate_blob_gas_price(evm.env.excess_blob_gas) + blob_base_fee = calculate_blob_gas_price( + evm.message.block_env.excess_blob_gas + ) push(evm.stack, U256(blob_base_fee)) # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/instructions/storage.py b/src/ethereum/prague/vm/instructions/storage.py index f88e295736..65a0d5a9b6 100644 --- a/src/ethereum/prague/vm/instructions/storage.py +++ b/src/ethereum/prague/vm/instructions/storage.py @@ -56,7 +56,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -80,10 +82,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -123,7 +126,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) @@ -146,7 +149,7 @@ def tload(evm: Evm) -> None: # OPERATION value = get_transient_storage( - evm.env.transient_storage, evm.message.current_target, key + evm.message.tx_env.transient_storage, evm.message.current_target, key ) push(evm.stack, value) @@ -171,7 +174,10 @@ def tstore(evm: Evm) -> None: if evm.message.is_static: raise WriteInStaticContext set_transient_storage( - evm.env.transient_storage, evm.message.current_target, key, new_value + evm.message.tx_env.transient_storage, + evm.message.current_target, + key, + new_value, ) # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index a75e036f06..88ba466e8e 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -94,7 +94,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -106,15 +106,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -129,9 +133,8 @@ def generic_create( accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, - authorizations=(), ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -169,7 +172,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -301,7 +306,7 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code if is_delegated_code and len(code) == 0: evm.gas_left += gas @@ -309,6 +314,8 @@ def generic_call( return child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -323,9 +330,8 @@ def generic_call( accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, - authorizations=(), ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -388,7 +394,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -404,7 +410,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -488,7 +494,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -535,8 +541,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -545,10 +554,12 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account(evm.env.state, originator).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance move_ether( - evm.env.state, + evm.message.block_env.state, originator, beneficiary, originator_balance, @@ -556,14 +567,14 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion only if it was created # in the same transaction - if originator in evm.env.state.created_accounts: + if originator in evm.message.block_env.state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index d105142452..5856c5af9c 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm.eoa_delegation import set_delegation from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -91,9 +91,7 @@ class MessageCallOutput: return_data: Bytes -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -103,19 +101,17 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), @@ -127,12 +123,14 @@ def process_message_call( Bytes(b""), ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - if message.authorizations != (): - refund_counter += set_delegation(message, env) - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + if message.tx_env.authorizations != (): + refund_counter += set_delegation(message) + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -161,7 +159,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -177,8 +175,10 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.prague.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage # take snapshot of state before processing the message - begin_transaction(env.state, env.transient_storage) + begin_transaction(state, transient_storage) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -187,15 +187,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -207,19 +207,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state, env.transient_storage) + set_code(state, message.current_target, contract_code) + commit_transaction(state, transient_storage) else: - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -235,30 +235,32 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.prague.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state, env.transient_storage) + begin_transaction(state, transient_storage) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state, env.transient_storage) + rollback_transaction(state, transient_storage) else: - commit_transaction(env.state, env.transient_storage) + commit_transaction(state, transient_storage) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -283,7 +285,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index d8a28becfc..46e39874c1 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -16,7 +16,7 @@ from typing import List, Optional, Tuple, Union from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -29,14 +29,15 @@ from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Account, Address from .state import ( State, account_exists_and_is_empty, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, - process_withdrawal, + modify_state, set_account_balance, state_root, ) @@ -47,10 +48,11 @@ Transaction, decode_transaction, encode_transaction, + get_transaction_hash, recover_sender, validate_transaction, ) -from .trie import Trie, root, trie_set +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -156,36 +158,46 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) if block.ommers != (): raise InvalidBlock - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.base_fee_per_gas, - block.header.gas_limit, - block.header.timestamp, - block.header.prev_randao, - block.transactions, - chain.chain_id, - block.withdrawals, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + withdrawals=block.withdrawals, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + withdrawals_root = root(block_output.withdrawals_trie) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock - if apply_body_output.withdrawals_root != block.header.withdrawals_root: + if withdrawals_root != block.header.withdrawals_root: raise InvalidBlock chain.blocks.append(block) @@ -257,7 +269,7 @@ def calculate_base_fee_per_gas( return Uint(expected_base_fee_per_gas) -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -270,11 +282,27 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + if header.gas_used > header.gas_limit: raise InvalidBlock @@ -305,24 +333,21 @@ def validate_header(header: Header, parent_header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - base_fee_per_gas: Uint, - gas_available: Uint, - chain_id: U64, ) -> Tuple[Address, Uint]: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - base_fee_per_gas : - The block base fee. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -336,25 +361,36 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) if isinstance(tx, FeeMarketTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise InvalidBlock - if tx.max_fee_per_gas < base_fee_per_gas: + if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InvalidBlock priority_fee_per_gas = min( tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - base_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, ) - effective_gas_price = priority_fee_per_gas + base_fee_per_gas + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas else: - if tx.gas_price < base_fee_per_gas: + if tx.gas_price < block_env.base_fee_per_gas: raise InvalidBlock effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address, effective_gas_price @@ -395,49 +431,11 @@ def make_receipt( return encode_receipt(tx, receipt) -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - withdrawals_root : `ethereum.fork_types.Root` - Trie root of all the withdrawals in the block. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - withdrawals_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - base_fee_per_gas: Uint, - block_gas_limit: Uint, - block_time: U256, - prev_randao: Bytes32, + block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - chain_id: U64, withdrawals: Tuple[Withdrawal, ...], -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -450,115 +448,36 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - base_fee_per_gas : - Base fee per gas of within the block. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - prev_randao : - The previous randao from the beacon chain. + block_env : + The block scoped environment. + block_output : + The block output for the current block. transactions : Transactions included in the block. - ommers : - Headers of ancestor blocks which are not direct parents (formerly - uncles.) - chain_id : - ID of the executing chain. withdrawals : Withdrawals to be processed in the current block. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = Trie(secured=False, default=None) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = Trie( - secured=False, default=None - ) - withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(map(decode_transaction, transactions)): - trie_set( - transactions_trie, rlp.encode(Uint(i)), encode_transaction(tx) - ) - - sender_address, effective_gas_price = check_transaction( - tx, base_fee_per_gas, gas_available, chain_id - ) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - base_fee_per_gas=base_fee_per_gas, - gas_price=effective_gas_price, - time=block_time, - prev_randao=prev_randao, - state=state, - chain_id=chain_id, - traces=[], - ) - - gas_used, logs, error = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, error, (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs - - block_gas_used = block_gas_limit - gas_available + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs_bloom = logs_bloom(block_logs) + process_withdrawals(block_env, block_output, withdrawals) - for i, wd in enumerate(withdrawals): - trie_set(withdrawals_trie, rlp.encode(Uint(i)), rlp.encode(wd)) - - process_withdrawal(state, wd) - - if account_exists_and_is_empty(state, wd.address): - destroy_account(state, wd.address) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - root(withdrawals_trie), - ) + return block_output def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...], Optional[EthereumException]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -573,103 +492,142 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) + ( + sender, + effective_gas_price, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) - if isinstance(tx, FeeMarketTransaction): - max_gas_fee = tx.gas * tx.max_fee_per_gas - else: - max_gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender_account = get_account(block_env.state, sender) - effective_gas_fee = tx.gas * env.gas_price + effective_gas_fee = tx.gas * effective_gas_price gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) sender_balance_after_gas_fee = ( Uint(sender_account.balance) - effective_gas_fee ) - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) + ) - preaccessed_addresses = set() - preaccessed_storage_keys = set() - preaccessed_addresses.add(env.coinbase) + access_list_addresses = set() + access_list_storage_keys = set() + access_list_addresses.add(block_env.coinbase) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for address, keys in tx.access_list: - preaccessed_addresses.add(address) + access_list_addresses.add(address) for key in keys: - preaccessed_storage_keys.add((address, key)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, - preaccessed_addresses=frozenset(preaccessed_addresses), - preaccessed_storage_keys=frozenset(preaccessed_storage_keys), + access_list_storage_keys.add((address, key)) + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], ) - output = process_message_call(message, env) + message = prepare_message(block_env, tx_env, tx) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(5), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * env.gas_price + tx_output = process_message_call(message) - # For non-1559 transactions env.gas_price == tx.gas_price - priority_fee_per_gas = env.gas_price - env.base_fee_per_gas - transaction_fee = ( - tx.gas - output.gas_left - gas_refund - ) * priority_fee_per_gas + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(5), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price - total_gas_used = gas_used - gas_refund + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * priority_fee_per_gas # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) + + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) + + block_output.block_logs += tx_output.logs - for address in output.accounts_to_delete: - destroy_account(env.state, address) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) +def process_withdrawals( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + withdrawals: Tuple[Withdrawal, ...], +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) + + modify_state(block_env.state, wd.address, increase_recipient_balance) - return total_gas_used, output.logs, output.error + if account_exists_and_is_empty(block_env.state, wd.address): + destroy_account(block_env.state, wd.address) def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/shanghai/state.py b/src/ethereum/shanghai/state.py index 248a4adbaa..3650999343 100644 --- a/src/ethereum/shanghai/state.py +++ b/src/ethereum/shanghai/state.py @@ -17,13 +17,12 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify from ethereum_types.numeric import U256, Uint -from .blocks import Withdrawal from .fork_types import EMPTY_ACCOUNT, Account, Address, Root from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set @@ -502,20 +501,6 @@ def increase_recipient_balance(recipient: Account) -> None: modify_state(state, recipient_address, increase_recipient_balance) -def process_withdrawal( - state: State, - wd: Withdrawal, -) -> None: - """ - Increase the balance of the withdrawing account. - """ - - def increase_recipient_balance(recipient: Account) -> None: - recipient.balance += wd.amount * U256(10**9) - - modify_state(state, wd.address, increase_recipient_balance) - - def set_account_balance(state: State, address: Address, amount: U256) -> None: """ Sets the balance of an account. @@ -626,3 +611,20 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index 16c5a73044..06cfa077ac 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -9,7 +9,7 @@ from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 @@ -18,12 +18,12 @@ from .exceptions import TransactionTypeError from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 16 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 -TX_ACCESS_LIST_ADDRESS_COST = 2400 -TX_ACCESS_LIST_STORAGE_KEY_COST = 1900 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(16) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) @slotted_freezable @@ -182,12 +182,12 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ from .vm.gas import init_code_cost - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -196,17 +196,17 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: data_cost += TX_DATA_COST_PER_NON_ZERO if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) else: - create_cost = 0 + create_cost = Uint(0) - access_list_cost = 0 + access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): for _address, keys in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + return TX_BASE_COST + data_cost + create_cost + access_list_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -391,3 +391,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/shanghai/utils/message.py b/src/ethereum/shanghai/utils/message.py index 2d5c3b50ef..07832dd672 100644 --- a/src/ethereum/shanghai/utils/message.py +++ b/src/ethereum/shanghai/utils/message.py @@ -12,64 +12,33 @@ Message specific functions used in this shanghai version of specification. """ -from typing import FrozenSet, Optional, Tuple, Union - -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.numeric import U256, Uint +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, - is_static: bool = False, - preaccessed_addresses: FrozenSet[Address] = frozenset(), - preaccessed_storage_keys: FrozenSet[ - Tuple[(Address, Bytes32)] - ] = frozenset(), + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. - is_static: - if True then it prevents all state-changing operations from being - executed. - preaccessed_addresses: - Addresses that should be marked as accessed prior to the message call - preaccessed_storage_keys: - Storage keys that should be marked as accessed prior to the message - call + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- @@ -77,40 +46,44 @@ def prepare_message( Items containing contract creation or message call specific data. """ accessed_addresses = set() - accessed_addresses.add(caller) + accessed_addresses.add(tx_env.origin) accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(preaccessed_addresses) - if isinstance(target, Bytes0): + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") accessed_addresses.add(current_target) return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=is_static, + should_transfer_value=True, + is_static=False, accessed_addresses=accessed_addresses, - accessed_storage_keys=set(preaccessed_storage_keys), + accessed_storage_keys=set(tx_env.access_list_storage_keys), parent_evm=None, ) diff --git a/src/ethereum/shanghai/vm/__init__.py b/src/ethereum/shanghai/vm/__init__.py index 920c234dd4..3d48ccaafe 100644 --- a/src/ethereum/shanghai/vm/__init__.py +++ b/src/ethereum/shanghai/vm/__init__.py @@ -13,7 +13,7 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0, Bytes32 @@ -22,32 +22,79 @@ from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint base_fee_per_gas: Uint - gas_limit: Uint - gas_price: Uint time: U256 prev_randao: Bytes32 - state: State - chain_id: U64 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + withdrawals_trie : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] traces: List[dict] @@ -57,6 +104,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -82,7 +131,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -114,7 +162,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) @@ -143,7 +191,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/shanghai/vm/instructions/block.py b/src/ethereum/shanghai/vm/instructions/block.py index ca4fc42958..4eaee1b02d 100644 --- a/src/ethereum/shanghai/vm/instructions/block.py +++ b/src/ethereum/shanghai/vm/instructions/block.py @@ -44,13 +44,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -85,7 +91,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -118,7 +124,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -150,7 +156,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -182,7 +188,7 @@ def prev_randao(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.prev_randao)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -214,7 +220,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -243,7 +249,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.chain_id)) + push(evm.stack, U256(evm.message.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/shanghai/vm/instructions/environment.py b/src/ethereum/shanghai/vm/instructions/environment.py index dc59e168bd..172ce97d70 100644 --- a/src/ethereum/shanghai/vm/instructions/environment.py +++ b/src/ethereum/shanghai/vm/instructions/environment.py @@ -82,7 +82,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -108,7 +108,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -319,7 +319,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -348,7 +348,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -390,7 +390,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) @@ -476,7 +476,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - account = get_account(evm.env.state, address) + account = get_account(evm.message.block_env.state, address) if account == EMPTY_ACCOUNT: codehash = U256(0) @@ -508,7 +508,9 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, evm.message.current_target).balance + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance push(evm.stack, balance) @@ -533,7 +535,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.base_fee_per_gas)) + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/shanghai/vm/instructions/storage.py b/src/ethereum/shanghai/vm/instructions/storage.py index c1c84399d9..319162b381 100644 --- a/src/ethereum/shanghai/vm/instructions/storage.py +++ b/src/ethereum/shanghai/vm/instructions/storage.py @@ -50,7 +50,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_COLD_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -74,10 +76,11 @@ 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( - evm.env.state, evm.message.current_target, key + state, evm.message.current_target, key ) - current_value = get_storage(evm.env.state, evm.message.current_target, key) + current_value = get_storage(state, evm.message.current_target, key) gas_cost = Uint(0) @@ -117,7 +120,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) if evm.message.is_static: raise WriteInStaticContext - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 2498d11792..f214469225 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -92,7 +92,7 @@ def generic_create( evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) if ( sender.balance < endowment @@ -104,15 +104,19 @@ def generic_create( return if account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) return - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce(evm.message.block_env.state, evm.message.current_target) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -128,7 +132,7 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -166,7 +170,9 @@ def create(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) generic_create( @@ -296,8 +302,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -313,7 +321,7 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -369,7 +377,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if is_account_alive(evm.env.state, to) or value == 0 + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -385,7 +393,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -459,7 +467,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -504,8 +512,11 @@ def selfdestruct(evm: Evm) -> None: gas_cost += GAS_COLD_ACCOUNT_ACCESS if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -514,23 +525,29 @@ def selfdestruct(evm: Evm) -> None: raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/shanghai/vm/interpreter.py b/src/ethereum/shanghai/vm/interpreter.py index 4c4ac75e26..431bda94f9 100644 --- a/src/ethereum/shanghai/vm/interpreter.py +++ b/src/ethereum/shanghai/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,9 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -101,28 +99,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -150,7 +148,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -166,8 +164,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.shanghai.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -176,15 +175,15 @@ def process_create_message(message: Message, env: Environment) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, 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. - mark_account_created(env.state, message.current_target) + mark_account_created(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -196,19 +195,19 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.output = b"" evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -224,30 +223,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.shanghai.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -272,7 +272,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index 420fce593e..77c4f74360 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -11,12 +11,11 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass -from typing import List, Optional, Set, Tuple +from typing import List, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -26,19 +25,25 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, account_exists_and_is_empty, create_ether, destroy_account, + destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -144,32 +149,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, - chain.chain_id, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -179,7 +193,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -192,11 +206,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -295,21 +328,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, - chain_id: U64, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. - chain_id : - The ID of the current chain. Returns ------- @@ -321,15 +354,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock - sender_address = recover_sender(chain_id, tx) + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, post_state: Bytes32, cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -339,8 +382,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. post_state : The state root immediately after this transaction. cumulative_gas_used : @@ -364,45 +405,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], - chain_id: U64, -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -415,90 +422,27 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : Headers of ancestor blocks which are not direct parents (formerly uncles.) - chain_id : - ID of the executing chain. Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available, chain_id) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, state_root(state), (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) + process_transaction(block_env, block_output, tx, Uint(i)) - block_logs += logs + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - pay_rewards(state, block_number, coinbase, ommers) - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -537,10 +481,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -622,8 +563,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -638,77 +582,95 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) if coinbase_balance_after_mining_fee != 0: set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, + block_env.coinbase, + coinbase_balance_after_mining_fee, ) - elif account_exists_and_is_empty(env.state, env.coinbase): - destroy_account(env.state, env.coinbase) + elif account_exists_and_is_empty(block_env.state, block_env.coinbase): + destroy_account(block_env.state, block_env.coinbase) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - for address in output.touched_accounts: - if account_exists_and_is_empty(env.state, address): - destroy_account(env.state, address) + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + state_root(block_env.state), + block_output.block_gas_used, + tx_output.logs, + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/spurious_dragon/state.py b/src/ethereum/spurious_dragon/state.py index 85d5cc773b..e1928ac86f 100644 --- a/src/ethereum/spurious_dragon/state.py +++ b/src/ethereum/spurious_dragon/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -572,3 +572,20 @@ def increase_balance(account: Account) -> None: account.balance += amount modify_state(state, address, increase_balance) + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Iterable[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Iterable[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index 1c67eeaf02..21ffbc1b6f 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(chain_id: U64, tx: Transaction) -> Address: @@ -157,6 +157,7 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: public_key = secp256k1_recover( r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) ) + return Address(keccak256(public_key)[12:32]) @@ -219,3 +220,18 @@ def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/spurious_dragon/utils/message.py b/src/ethereum/spurious_dragon/utils/message.py index de8b274d5d..c513ddfc6d 100644 --- a/src/ethereum/spurious_dragon/utils/message.py +++ b/src/ethereum/spurious_dragon/utils/message.py @@ -12,82 +12,67 @@ Message specific functions used in this spurious dragon version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.spurious_dragon.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, + should_transfer_value=True, parent_evm=None, ) diff --git a/src/ethereum/spurious_dragon/vm/__init__.py b/src/ethereum/spurious_dragon/vm/__init__.py index f811355b73..b63a25edab 100644 --- a/src/ethereum/spurious_dragon/vm/__init__.py +++ b/src/ethereum/spurious_dragon/vm/__init__.py @@ -13,39 +13,80 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State, account_exists_and_is_empty +from ..transactions import Transaction +from ..trie import Trie from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -55,6 +96,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -77,7 +120,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -106,7 +148,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.touched_accounts.update(child_evm.touched_accounts) if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(child_evm.message.current_target) @@ -133,7 +175,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: evm.touched_accounts.add(RIPEMD160_ADDRESS) if child_evm.message.current_target == RIPEMD160_ADDRESS: if account_exists_and_is_empty( - evm.env.state, child_evm.message.current_target + evm.message.block_env.state, child_evm.message.current_target ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/spurious_dragon/vm/instructions/block.py b/src/ethereum/spurious_dragon/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/block.py +++ b/src/ethereum/spurious_dragon/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/spurious_dragon/vm/instructions/environment.py b/src/ethereum/spurious_dragon/vm/instructions/environment.py index 1bce21fda7..36215ecb1a 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/environment.py +++ b/src/ethereum/spurious_dragon/vm/instructions/environment.py @@ -73,7 +73,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -99,7 +99,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -310,7 +310,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -333,8 +333,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -369,7 +368,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/spurious_dragon/vm/instructions/storage.py b/src/ethereum/spurious_dragon/vm/instructions/storage.py index 7b299e07d0..76c11bcfe4 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/storage.py +++ b/src/ethereum/spurious_dragon/vm/instructions/storage.py @@ -44,7 +44,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -67,7 +69,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -79,7 +82,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/spurious_dragon/vm/instructions/system.py b/src/ethereum/spurious_dragon/vm/instructions/system.py index 3ce0780272..a4d809e110 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/system.py +++ b/src/ethereum/spurious_dragon/vm/instructions/system.py @@ -80,11 +80,13 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -95,18 +97,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -119,7 +127,7 @@ def create(evm: Evm) -> None: should_transfer_value=True, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -192,8 +200,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -206,7 +216,7 @@ def generic_call( should_transfer_value=should_transfer_value, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -254,7 +264,7 @@ def call(evm: Evm) -> None: create_gas_cost = ( Uint(0) - if value == 0 or is_account_alive(evm.env.state, to) + if is_account_alive(evm.message.block_env.state, to) or value == 0 else GAS_NEW_ACCOUNT ) transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE @@ -270,7 +280,7 @@ def call(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -335,7 +345,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -374,8 +384,11 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT if ( - not is_account_alive(evm.env.state, beneficiary) - and get_account(evm.env.state, evm.message.current_target).balance != 0 + not is_account_alive(evm.message.block_env.state, beneficiary) + and get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + != 0 ): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT @@ -392,24 +405,29 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost) - # OPERATION - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.env.state, beneficiary): + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution diff --git a/src/ethereum/spurious_dragon/vm/interpreter.py b/src/ethereum/spurious_dragon/vm/interpreter.py index 17d8f57f11..cdb2bbcc53 100644 --- a/src/ethereum/spurious_dragon/vm/interpreter.py +++ b/src/ethereum/spurious_dragon/vm/interpreter.py @@ -48,7 +48,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -86,9 +86,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -98,28 +96,28 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) - if account_exists_and_is_empty(env.state, Address(message.target)): + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): evm.touched_accounts.add(Address(message.target)) if evm.error: @@ -147,7 +145,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -163,8 +161,9 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.spurious_dragon.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely @@ -172,10 +171,10 @@ def process_create_message(message: Message, env: Environment) -> Evm: # * The address created by two `CREATE` calls collide. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - increment_nonce(env.state, message.current_target) - evm = process_message(message, env) + increment_nonce(state, message.current_target) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT @@ -184,18 +183,18 @@ def process_create_message(message: Message, env: Environment) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -211,30 +210,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.spurious_dragon.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -259,7 +259,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index cf39821f14..9a13f28c3b 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -11,12 +11,11 @@ Entry point for the Ethereum specification. """ - from dataclasses import dataclass -from typing import List, Optional, Set, Tuple +from typing import List, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -26,7 +25,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom -from .fork_types import Address, Bloom, Root +from .fork_types import Address from .state import ( State, create_ether, @@ -36,8 +35,13 @@ set_account_balance, state_root, ) -from .transactions import Transaction, recover_sender, validate_transaction -from .trie import Trie, root, trie_set +from .transactions import ( + Transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set from .utils.message import prepare_message from .vm.interpreter import process_message_call @@ -143,31 +147,41 @@ def state_transition(chain: BlockChain, block: Block) -> None: block : Block to apply to `chain`. """ - parent_header = chain.blocks[-1].header - validate_header(block.header, parent_header) + validate_header(chain, block.header) validate_ommers(block.ommers, block.header, chain) - apply_body_output = apply_body( - chain.state, - get_last_256_block_hashes(chain), - block.header.coinbase, - block.header.number, - block.header.gas_limit, - block.header.timestamp, - block.header.difficulty, - block.transactions, - block.ommers, + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + time=block.header.timestamp, + difficulty=block.header.difficulty, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + ommers=block.ommers, ) - if apply_body_output.block_gas_used != block.header.gas_used: + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + + if block_output.block_gas_used != block.header.gas_used: raise InvalidBlock( - f"{apply_body_output.block_gas_used} != {block.header.gas_used}" + f"{block_output.block_gas_used} != {block.header.gas_used}" ) - if apply_body_output.transactions_root != block.header.transactions_root: + if transactions_root != block.header.transactions_root: raise InvalidBlock - if apply_body_output.state_root != block.header.state_root: + if block_state_root != block.header.state_root: raise InvalidBlock - if apply_body_output.receipt_root != block.header.receipt_root: + if receipt_root != block.header.receipt_root: raise InvalidBlock - if apply_body_output.block_logs_bloom != block.header.bloom: + if block_logs_bloom != block.header.bloom: raise InvalidBlock chain.blocks.append(block) @@ -177,7 +191,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: chain.blocks = chain.blocks[-255:] -def validate_header(header: Header, parent_header: Header) -> None: +def validate_header(chain: BlockChain, header: Header) -> None: """ Verifies a block header. @@ -190,11 +204,30 @@ def validate_header(header: Header, parent_header: Header) -> None: Parameters ---------- + chain : + History and current state. header : Header to check for correctness. - parent_header : - Parent Header of the header to check for correctness """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + if header.gas_used > header.gas_limit: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -293,18 +326,21 @@ def validate_proof_of_work(header: Header) -> None: def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, tx: Transaction, - gas_available: Uint, ) -> Address: """ Check if the transaction is includable in the block. Parameters ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. tx : The transaction. - gas_available : - The gas remaining in the block. Returns ------- @@ -316,15 +352,25 @@ def check_transaction( InvalidBlock : If the transaction is not includable. """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise InvalidBlock sender_address = recover_sender(tx) + sender_account = get_account(block_env.state, sender_address) + + max_gas_fee = tx.gas * tx.gas_price + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code: + raise InvalidSenderError("not EOA") return sender_address def make_receipt( - tx: Transaction, post_state: Bytes32, cumulative_gas_used: Uint, logs: Tuple[Log, ...], @@ -334,8 +380,6 @@ def make_receipt( Parameters ---------- - tx : - The executed transaction. post_state : The state root immediately after this transaction. cumulative_gas_used : @@ -359,44 +403,11 @@ def make_receipt( return receipt -@dataclass -class ApplyBodyOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_root : `ethereum.fork_types.Root` - Trie root of all the transactions in the block. - receipt_root : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs_bloom : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - state_root : `ethereum.fork_types.Root` - State root after all transactions have been executed. - """ - - block_gas_used: Uint - transactions_root: Root - receipt_root: Root - block_logs_bloom: Bloom - state_root: Root - - def apply_body( - state: State, - block_hashes: List[Hash32], - coinbase: Address, - block_number: Uint, - block_gas_limit: Uint, - block_time: U256, - block_difficulty: Uint, + block_env: vm.BlockEnvironment, transactions: Tuple[Transaction, ...], ommers: Tuple[Header, ...], -) -> ApplyBodyOutput: +) -> vm.BlockOutput: """ Executes a block. @@ -409,21 +420,8 @@ def apply_body( Parameters ---------- - state : - Current account state. - block_hashes : - List of hashes of the previous 256 blocks in the order of - increasing block number. - coinbase : - Address of account which receives block reward and transaction fees. - block_number : - Position of the block within the chain. - block_gas_limit : - Initial amount of gas available for execution in this block. - block_time : - Time the block was produced, measured in seconds since the epoch. - block_difficulty : - Difficulty of the block. + block_env : + The block scoped environment. transactions : Transactions included in the block. ommers : @@ -432,65 +430,17 @@ def apply_body( Returns ------- - apply_body_output : `ApplyBodyOutput` - Output of applying the block body to the state. + block_output : + The block output for the current block. """ - gas_available = block_gas_limit - transactions_trie: Trie[Bytes, Optional[Transaction]] = Trie( - secured=False, default=None - ) - receipts_trie: Trie[Bytes, Optional[Receipt]] = Trie( - secured=False, default=None - ) - block_logs: Tuple[Log, ...] = () + block_output = vm.BlockOutput() for i, tx in enumerate(transactions): - trie_set(transactions_trie, rlp.encode(Uint(i)), tx) - - sender_address = check_transaction(tx, gas_available) - - env = vm.Environment( - caller=sender_address, - origin=sender_address, - block_hashes=block_hashes, - coinbase=coinbase, - number=block_number, - gas_limit=block_gas_limit, - gas_price=tx.gas_price, - time=block_time, - difficulty=block_difficulty, - state=state, - traces=[], - ) - - gas_used, logs = process_transaction(env, tx) - gas_available -= gas_used - - receipt = make_receipt( - tx, state_root(state), (block_gas_limit - gas_available), logs - ) - - trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - - block_logs += logs + process_transaction(block_env, block_output, tx, Uint(i)) - pay_rewards(state, block_number, coinbase, ommers) + pay_rewards(block_env.state, block_env.number, block_env.coinbase, ommers) - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = logs_bloom(block_logs) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - ) + return block_output def validate_ommers( @@ -529,10 +479,7 @@ def validate_ommers( for ommer in ommers: if Uint(1) > ommer.number or ommer.number >= block_header.number: raise InvalidBlock - ommer_parent_header = chain.blocks[ - -(block_header.number - ommer.number) - 1 - ].header - validate_header(ommer, ommer_parent_header) + validate_header(chain, ommer) if len(ommers) > 2: raise InvalidBlock @@ -614,8 +561,11 @@ def pay_rewards( def process_transaction( - env: vm.Environment, tx: Transaction -) -> Tuple[Uint, Tuple[Log, ...]]: + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: """ Execute a transaction against the provided environment. @@ -630,70 +580,88 @@ def process_transaction( Parameters ---------- - env : + block_env : Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. tx : Transaction to execute. - - Returns - ------- - gas_left : `ethereum.base_types.U256` - Remaining gas after execution. - logs : `Tuple[ethereum.blocks.Log, ...]` - Logs generated during execution. + index: + Index of the transaction in the block. """ + trie_set(block_output.transactions_trie, rlp.encode(Uint(index)), tx) intrinsic_gas = validate_transaction(tx) - sender = env.origin - sender_account = get_account(env.state, sender) - gas_fee = tx.gas * tx.gas_price - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code != bytearray(): - raise InvalidSenderError("not EOA") + sender = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) gas = tx.gas - intrinsic_gas - increment_nonce(env.state, sender) + increment_nonce(block_env.state, sender) + + gas_fee = tx.gas * tx.gas_price sender_balance_after_gas_fee = Uint(sender_account.balance) - gas_fee - set_account_balance(env.state, sender, U256(sender_balance_after_gas_fee)) - - message = prepare_message( - sender, - tx.to, - tx.value, - tx.data, - gas, - env, + set_account_balance( + block_env.state, sender, U256(sender_balance_after_gas_fee) ) - output = process_message_call(message, env) + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=tx.gas_price, + gas=gas, + index_in_block=index, + tx_hash=get_transaction_hash(tx), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) - gas_used = tx.gas - output.gas_left - gas_refund = min(gas_used // Uint(2), Uint(output.refund_counter)) - gas_refund_amount = (output.gas_left + gas_refund) * tx.gas_price - transaction_fee = (tx.gas - output.gas_left - gas_refund) * tx.gas_price - total_gas_used = gas_used - gas_refund + gas_used = tx.gas - tx_output.gas_left + gas_refund = min(gas_used // Uint(2), Uint(tx_output.refund_counter)) + tx_gas_used = gas_used - gas_refund + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * tx.gas_price + + transaction_fee = tx_gas_used * tx.gas_price # refund gas sender_balance_after_refund = get_account( - env.state, sender + block_env.state, sender ).balance + U256(gas_refund_amount) - set_account_balance(env.state, sender, sender_balance_after_refund) + set_account_balance(block_env.state, sender, sender_balance_after_refund) # transfer miner fees coinbase_balance_after_mining_fee = get_account( - env.state, env.coinbase + block_env.state, block_env.coinbase ).balance + U256(transaction_fee) set_account_balance( - env.state, env.coinbase, coinbase_balance_after_mining_fee + block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee ) - for address in output.accounts_to_delete: - destroy_account(env.state, address) + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + block_output.block_gas_used += tx_gas_used + + receipt = make_receipt( + state_root(block_env.state), + block_output.block_gas_used, + tx_output.logs, + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) - return total_gas_used, output.logs + block_output.block_logs += tx_output.logs def compute_header_hash(header: Header) -> Hash32: diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index 96d825e2ba..6db00e736d 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -17,10 +17,10 @@ from .fork_types import Address -TX_BASE_COST = 21000 -TX_DATA_COST_PER_NON_ZERO = 68 -TX_DATA_COST_PER_ZERO = 4 -TX_CREATE_COST = 32000 +TX_BASE_COST = Uint(21000) +TX_DATA_COST_PER_NON_ZERO = Uint(68) +TX_DATA_COST_PER_ZERO = Uint(4) +TX_CREATE_COST = Uint(32000) @slotted_freezable @@ -99,10 +99,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: Returns ------- - verified : `ethereum.base_types.Uint` + intrinsic_gas : `ethereum.base_types.Uint` The intrinsic cost of the transaction. """ - data_cost = 0 + data_cost = Uint(0) for byte in tx.data: if byte == 0: @@ -113,9 +113,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if tx.to == Bytes0(b""): create_cost = TX_CREATE_COST else: - create_cost = 0 + create_cost = Uint(0) - return Uint(TX_BASE_COST + data_cost + create_cost) + return TX_BASE_COST + data_cost + create_cost def recover_sender(tx: Transaction) -> Address: @@ -180,3 +180,18 @@ def signing_hash(tx: Transaction) -> Hash32: ) ) ) + + +def get_transaction_hash(tx: Transaction) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256(rlp.encode(tx)) diff --git a/src/ethereum/tangerine_whistle/utils/message.py b/src/ethereum/tangerine_whistle/utils/message.py index b8821422ce..47b84c1af2 100644 --- a/src/ethereum/tangerine_whistle/utils/message.py +++ b/src/ethereum/tangerine_whistle/utils/message.py @@ -12,82 +12,67 @@ Message specific functions used in this tangerine whistle version of specification. """ -from typing import Optional, Union - from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import Uint from ..fork_types import Address from ..state import get_account -from ..vm import Environment, Message +from ..transactions import Transaction +from ..vm import BlockEnvironment, Message, TransactionEnvironment from .address import compute_contract_address def prepare_message( - caller: Address, - target: Union[Bytes0, Address], - value: U256, - data: Bytes, - gas: Uint, - env: Environment, - code_address: Optional[Address] = None, - should_transfer_value: bool = True, + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, ) -> Message: """ Execute a transaction against the provided environment. Parameters ---------- - caller : - Address which initiated the transaction - target : - Address whose code will be executed - value : - Value to be transferred. - data : - Array of bytes provided to the code in `target`. - gas : - Gas provided for the code in `target`. - env : + block_env : Environment for the Ethereum Virtual Machine. - code_address : - This is usually same as the `target` address except when an alternative - accounts code needs to be executed. - eg. `CALLCODE` calling a precompile. - should_transfer_value : - if True ETH should be transferred while executing a message call. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. Returns ------- message: `ethereum.tangerine_whistle.vm.Message` Items containing contract creation or message call specific data. """ - if isinstance(target, Bytes0): + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( - caller, - get_account(env.state, caller).nonce - Uint(1), + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), ) msg_data = Bytes(b"") - code = data - elif isinstance(target, Address): - current_target = target - msg_data = data - code = get_account(env.state, target).code - if code_address is None: - code_address = target + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") return Message( - caller=caller, - target=target, - gas=gas, - value=value, + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, data=msg_data, code=code, depth=Uint(0), current_target=current_target, code_address=code_address, - should_transfer_value=should_transfer_value, + should_transfer_value=True, parent_evm=None, ) diff --git a/src/ethereum/tangerine_whistle/vm/__init__.py b/src/ethereum/tangerine_whistle/vm/__init__.py index 4cd35a43f0..d351223871 100644 --- a/src/ethereum/tangerine_whistle/vm/__init__.py +++ b/src/ethereum/tangerine_whistle/vm/__init__.py @@ -13,38 +13,79 @@ `.fork_types.Account`. """ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, Union from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.exceptions import EthereumException -from ..blocks import Log +from ..blocks import Log, Receipt from ..fork_types import Address from ..state import State +from ..transactions import Transaction +from ..trie import Trie __all__ = ("Environment", "Evm", "Message") @dataclass -class Environment: +class BlockEnvironment: """ Items external to the virtual machine itself, provided by the environment. """ - caller: Address + chain_id: U64 + state: State + block_gas_limit: Uint block_hashes: List[Hash32] - origin: Address coinbase: Address number: Uint - gas_limit: Uint - gas_price: Uint time: U256 difficulty: Uint - state: State + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[Bytes, Optional[Transaction]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipts_trie: Trie[Bytes, Optional[Receipt]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + index_in_block: Uint + tx_hash: Optional[Hash32] traces: List[dict] @@ -54,6 +95,8 @@ class Message: Items that are used by contract creation or message call. """ + block_env: BlockEnvironment + tx_env: TransactionEnvironment caller: Address target: Union[Bytes0, Address] current_target: Address @@ -76,7 +119,6 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint - env: Environment valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int diff --git a/src/ethereum/tangerine_whistle/vm/instructions/block.py b/src/ethereum/tangerine_whistle/vm/instructions/block.py index bec65654b1..fc9bd51a23 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/block.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/block.py @@ -38,13 +38,19 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - if evm.env.number <= block_number or evm.env.number > max_block_number: + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): # Default hash to 0, if the block of interest is not yet on the chain # (including the block which has the current executing transaction), # or if the block's age is more than 256. hash = b"\x00" else: - hash = evm.env.block_hashes[-(evm.env.number - block_number)] + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] push(evm.stack, U256.from_be_bytes(hash)) @@ -73,7 +79,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -100,7 +106,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, evm.env.time) + push(evm.stack, evm.message.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +132,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.number)) + push(evm.stack, U256(evm.message.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -152,7 +158,7 @@ def difficulty(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.difficulty)) + push(evm.stack, U256(evm.message.block_env.difficulty)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,7 +184,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_limit)) + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/tangerine_whistle/vm/instructions/environment.py b/src/ethereum/tangerine_whistle/vm/instructions/environment.py index 1bce21fda7..36215ecb1a 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/environment.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/environment.py @@ -73,7 +73,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.env.state, address).balance + balance = get_account(evm.message.block_env.state, address).balance push(evm.stack, balance) @@ -99,7 +99,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.env.origin)) + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -310,7 +310,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GAS_BASE) # OPERATION - push(evm.stack, U256(evm.env.gas_price)) + push(evm.stack, U256(evm.message.tx_env.gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -333,8 +333,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, GAS_EXTERNAL) # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has empty code. - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code codesize = U256(len(code)) push(evm.stack, codesize) @@ -369,7 +368,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.env.state, address).code + code = get_account(evm.message.block_env.state, address).code value = buffer_read(code, code_start_index, size) memory_write(evm.memory, memory_start_index, value) diff --git a/src/ethereum/tangerine_whistle/vm/instructions/storage.py b/src/ethereum/tangerine_whistle/vm/instructions/storage.py index 7b299e07d0..76c11bcfe4 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/storage.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/storage.py @@ -44,7 +44,9 @@ def sload(evm: Evm) -> None: charge_gas(evm, GAS_SLOAD) # OPERATION - value = get_storage(evm.env.state, evm.message.current_target, key) + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) push(evm.stack, value) @@ -67,7 +69,8 @@ def sstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - current_value = get_storage(evm.env.state, evm.message.current_target, key) + state = evm.message.block_env.state + current_value = get_storage(state, evm.message.current_target, key) if new_value != 0 and current_value == 0: gas_cost = GAS_STORAGE_SET else: @@ -79,7 +82,7 @@ def sstore(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - set_storage(evm.env.state, evm.message.current_target, key, new_value) + set_storage(state, evm.message.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/tangerine_whistle/vm/instructions/system.py b/src/ethereum/tangerine_whistle/vm/instructions/system.py index b931d8e8eb..1d5b2fdd81 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/system.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/system.py @@ -79,11 +79,13 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_address = evm.message.current_target - sender = get_account(evm.env.state, sender_address) + sender = get_account(evm.message.block_env.state, sender_address) contract_address = compute_contract_address( evm.message.current_target, - get_account(evm.env.state, evm.message.current_target).nonce, + get_account( + evm.message.block_env.state, evm.message.current_target + ).nonce, ) if ( @@ -94,18 +96,24 @@ def create(evm: Evm) -> None: push(evm.stack, U256(0)) evm.gas_left += create_message_gas elif account_has_code_or_nonce( - evm.env.state, contract_address - ) or account_has_storage(evm.env.state, contract_address): - increment_nonce(evm.env.state, evm.message.current_target) + 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 + ) push(evm.stack, U256(0)) else: call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - increment_nonce(evm.env.state, evm.message.current_target) + increment_nonce( + evm.message.block_env.state, evm.message.current_target + ) child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, @@ -118,7 +126,7 @@ def create(evm: Evm) -> None: should_transfer_value=True, parent_evm=evm, ) - child_evm = process_create_message(child_message, evm.env) + child_evm = process_create_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -191,8 +199,10 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.env.state, code_address).code + code = get_account(evm.message.block_env.state, code_address).code child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, caller=caller, target=to, gas=gas, @@ -205,7 +215,7 @@ def generic_call( should_transfer_value=should_transfer_value, parent_evm=evm, ) - child_evm = process_message(child_message, evm.env) + child_evm = process_message(child_message) if child_evm.error: incorporate_child_on_error(evm, child_evm) @@ -251,7 +261,7 @@ def call(evm: Evm) -> None: code_address = to - _account_exists = account_exists(evm.env.state, to) + _account_exists = account_exists(evm.message.block_env.state, to) create_gas_cost = Uint(0) if _account_exists else GAS_NEW_ACCOUNT transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE message_call_gas = calculate_message_call_gas( @@ -266,7 +276,7 @@ def call(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -331,7 +341,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.message.block_env.state, evm.message.current_target ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -369,7 +379,7 @@ def selfdestruct(evm: Evm) -> None: # GAS gas_cost = GAS_SELF_DESTRUCT - if not account_exists(evm.env.state, beneficiary): + if not account_exists(evm.message.block_env.state, beneficiary): gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT originator = evm.message.current_target @@ -386,17 +396,23 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost) # OPERATION - beneficiary_balance = get_account(evm.env.state, beneficiary).balance - originator_balance = get_account(evm.env.state, originator).balance + beneficiary_balance = get_account( + evm.message.block_env.state, beneficiary + ).balance + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance # First Transfer to beneficiary set_account_balance( - evm.env.state, beneficiary, beneficiary_balance + originator_balance + evm.message.block_env.state, + beneficiary, + beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.env.state, originator, U256(0)) + set_account_balance(evm.message.block_env.state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) diff --git a/src/ethereum/tangerine_whistle/vm/interpreter.py b/src/ethereum/tangerine_whistle/vm/interpreter.py index f7b487a150..84693faf09 100644 --- a/src/ethereum/tangerine_whistle/vm/interpreter.py +++ b/src/ethereum/tangerine_whistle/vm/interpreter.py @@ -46,7 +46,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Evm from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -80,9 +80,7 @@ class MessageCallOutput: error: Optional[EthereumException] -def process_message_call( - message: Message, env: Environment -) -> MessageCallOutput: +def process_message_call(message: Message) -> MessageCallOutput: """ If `message.current` is empty then it creates a smart contract else it executes a call from the `message.caller` to the `message.target`. @@ -92,27 +90,25 @@ def process_message_call( message : Transaction specific items. - env : - External items required for EVM execution. - Returns ------- output : `MessageCallOutput` Output of the message call """ + block_env = message.block_env refund_counter = U256(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( - env.state, message.current_target - ) or account_has_storage(env.state, message.current_target) + block_env.state, message.current_target + ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: - evm = process_create_message(message, env) + evm = process_create_message(message) else: - evm = process_message(message, env) + evm = process_message(message) if evm.error: logs: Tuple[Log, ...] = () @@ -136,7 +132,7 @@ def process_message_call( ) -def process_create_message(message: Message, env: Environment) -> Evm: +def process_create_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -152,35 +148,36 @@ def process_create_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.tangerine_whistle.vm.Evm` Items containing execution specific objects. """ + state = message.block_env.state # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) # If the address where the account is being created has storage, it is # destroyed. This can only happen in the following highly unlikely # circumstances: # * The address created by two `CREATE` calls collide. # * The first `CREATE` left empty code. - destroy_storage(env.state, message.current_target) + destroy_storage(state, message.current_target) - evm = process_message(message, env) + evm = process_message(message) if not evm.error: contract_code = evm.output contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT try: charge_gas(evm, contract_code_gas) except ExceptionalHalt as error: - rollback_transaction(env.state) + rollback_transaction(state) evm.gas_left = Uint(0) evm.error = error else: - set_code(env.state, message.current_target, contract_code) - commit_transaction(env.state) + set_code(state, message.current_target, contract_code) + commit_transaction(state) else: - rollback_transaction(env.state) + rollback_transaction(state) return evm -def process_message(message: Message, env: Environment) -> Evm: +def process_message(message: Message) -> Evm: """ Executes a call to create a smart contract. @@ -196,30 +193,31 @@ def process_message(message: Message, env: Environment) -> Evm: evm: :py:class:`~ethereum.tangerine_whistle.vm.Evm` Items containing execution specific objects """ + state = message.block_env.state if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") # take snapshot of state before processing the message - begin_transaction(env.state) + begin_transaction(state) - touch_account(env.state, message.current_target) + touch_account(state, message.current_target) if message.should_transfer_value and message.value != 0: move_ether( - env.state, message.caller, message.current_target, message.value + state, message.caller, message.current_target, message.value ) - evm = execute_code(message, env) + evm = execute_code(message) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error - rollback_transaction(env.state) + rollback_transaction(state) else: - commit_transaction(env.state) + commit_transaction(state) return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message) -> Evm: """ Executes bytecode present in the `message`. @@ -244,7 +242,6 @@ def execute_code(message: Message, env: Environment) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, - env=env, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 2409e5a883..1a7af024a8 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -47,16 +47,6 @@ def is_after_fork(self, target_fork_name: str) -> bool: break return return_value - @property - def SYSTEM_TRANSACTION_GAS(self) -> Any: - """SYSTEM_TRANSACTION_GAS of the given fork.""" - return self._module("fork").SYSTEM_TRANSACTION_GAS - - @property - def SYSTEM_ADDRESS(self) -> Any: - """SYSTEM_ADDRESS of the given fork.""" - return self._module("fork").SYSTEM_ADDRESS - @property def BEACON_ROOTS_ADDRESS(self) -> Any: """BEACON_ROOTS_ADDRESS of the given fork.""" @@ -67,21 +57,6 @@ def HISTORY_STORAGE_ADDRESS(self) -> Any: """HISTORY_STORAGE_ADDRESS of the given fork.""" return self._module("fork").HISTORY_STORAGE_ADDRESS - @property - def WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: - """WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" - return self._module("fork").WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS - - @property - def CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS(self) -> Any: - """CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS of the given fork.""" - return self._module("fork").CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS - - @property - def HISTORY_SERVE_WINDOW(self) -> Any: - """HISTORY_SERVE_WINDOW of the given fork.""" - return self._module("fork").HISTORY_SERVE_WINDOW - @property def process_general_purpose_requests(self) -> Any: """process_general_purpose_requests function of the given fork.""" @@ -92,6 +67,11 @@ def process_system_transaction(self) -> Any: """process_system_transaction function of the given fork.""" return self._module("fork").process_system_transaction + @property + def process_withdrawals(self) -> Any: + """process_withdrawals function of the given fork.""" + return self._module("fork").process_withdrawals + @property def calculate_block_difficulty(self) -> Any: """calculate_block_difficulty function of the given fork.""" @@ -118,9 +98,9 @@ def state_transition(self) -> Any: return self._module("fork").state_transition @property - def make_receipt(self) -> Any: - """make_receipt function of the fork""" - return self._module("fork").make_receipt + def pay_rewards(self) -> Any: + """pay_rewards function of the fork""" + return self._module("fork").pay_rewards @property def signing_hash(self) -> Any: @@ -158,9 +138,9 @@ def signing_hash_4844(self) -> Any: return self._module("transactions").signing_hash_4844 @property - def check_transaction(self) -> Any: - """check_transaction function of the fork""" - return self._module("fork").check_transaction + def get_transaction_hash(self) -> Any: + """get_transaction_hash function of the fork""" + return self._module("transactions").get_transaction_hash @property def process_transaction(self) -> Any: @@ -178,9 +158,9 @@ def Block(self) -> Any: return self._module("blocks").Block @property - def parse_deposit_requests_from_receipt(self) -> Any: - """parse_deposit_requests_from_receipt function of the fork""" - return self._module("requests").parse_deposit_requests_from_receipt + def decode_receipt(self) -> Any: + """decode_receipt function of the fork""" + return self._module("blocks").decode_receipt @property def compute_requests_hash(self) -> Any: @@ -252,51 +232,16 @@ def State(self) -> Any: """State class of the fork""" return self._module("state").State - @property - def TransientStorage(self) -> Any: - """Transient storage class of the fork""" - return self._module("state").TransientStorage - - @property - def get_account(self) -> Any: - """get_account function of the fork""" - return self._module("state").get_account - @property def set_account(self) -> Any: """set_account function of the fork""" return self._module("state").set_account - @property - def create_ether(self) -> Any: - """create_ether function of the fork""" - return self._module("state").create_ether - @property def set_storage(self) -> Any: """set_storage function of the fork""" return self._module("state").set_storage - @property - def account_exists_and_is_empty(self) -> Any: - """account_exists_and_is_empty function of the fork""" - return self._module("state").account_exists_and_is_empty - - @property - def destroy_touched_empty_accounts(self) -> Any: - """destroy_account function of the fork""" - return self._module("state").destroy_touched_empty_accounts - - @property - def destroy_account(self) -> Any: - """destroy_account function of the fork""" - return self._module("state").destroy_account - - @property - def process_withdrawal(self) -> Any: - """process_withdrawal function of the fork""" - return self._module("state").process_withdrawal - @property def state_root(self) -> Any: """state_root function of the fork""" @@ -307,11 +252,6 @@ def close_state(self) -> Any: """close_state function of the fork""" return self._module("state").close_state - @property - def Trie(self) -> Any: - """Trie class of the fork""" - return self._module("trie").Trie - @property def root(self) -> Any: """Root function of the fork""" @@ -322,11 +262,6 @@ def copy_trie(self) -> Any: """copy_trie function of the fork""" return self._module("trie").copy_trie - @property - def trie_set(self) -> Any: - """trie_set function of the fork""" - return self._module("trie").trie_set - @property def hex_to_address(self) -> Any: """hex_to_address function of the fork""" @@ -338,35 +273,25 @@ def hex_to_root(self) -> Any: return self._module("utils.hexadecimal").hex_to_root @property - def Environment(self) -> Any: - """Environment class of the fork""" - return self._module("vm").Environment + def BlockEnvironment(self) -> Any: + """Block environment class of the fork""" + return self._module("vm").BlockEnvironment @property - def Message(self) -> Any: - """Message class of the fork""" - return self._module("vm").Message + def BlockOutput(self) -> Any: + """Block output class of the fork""" + return self._module("vm").BlockOutput @property def Authorization(self) -> Any: """Authorization class of the fork""" - return self._module("vm.eoa_delegation").Authorization + return self._module("fork_types").Authorization @property def TARGET_BLOB_GAS_PER_BLOCK(self) -> Any: """TARGET_BLOB_GAS_PER_BLOCK of the fork""" return self._module("vm.gas").TARGET_BLOB_GAS_PER_BLOCK - @property - def calculate_total_blob_gas(self) -> Any: - """calculate_total_blob_gas function of the fork""" - return self._module("vm.gas").calculate_total_blob_gas - - @property - def process_message_call(self) -> Any: - """process_message_call function of the fork""" - return self._module("vm.interpreter").process_message_call - @property def apply_dao(self) -> Any: """apply_dao function of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py index 196352655d..1e1ac04bcb 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py @@ -146,7 +146,7 @@ def run_test_case( t8n_options.output_basedir = output_basedir t8n = T8N(t8n_options, out_stream, in_stream) - t8n.apply_body() + t8n.run_state_test() return t8n.result diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index c835147d4d..4d9de304fc 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -9,12 +9,10 @@ from typing import Any, TextIO from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, Uint from ethereum import trace -from ethereum.crypto.hash import keccak256 -from ethereum.exceptions import EthereumException, InvalidBlock +from ethereum.exceptions import EthereumException from ethereum_spec_tools.forks import Hardfork from ..loaders.fixture_loader import Load @@ -26,7 +24,7 @@ parse_hex_or_int, ) from .env import Env -from .evm_trace import evm_trace, output_traces +from .evm_trace import evm_trace from .t8n_types import Alloc, Result, Txs @@ -127,50 +125,7 @@ def __init__( self.env.block_difficulty, self.env.base_fee_per_gas ) - @property - def BLOCK_REWARD(self) -> Any: - """ - For the t8n tool, the block reward is - provided as a command line option - """ - if self.options.state_reward < 0 or self.fork.is_after_fork( - "ethereum.paris" - ): - return None - else: - return U256(self.options.state_reward) - - def check_transaction(self, tx: Any, gas_available: Any) -> Any: - """ - Implements the check_transaction function of the fork. - The arguments to be passed are adjusted according to the fork. - """ - # TODO: The current PR changes the signature of the check_transaction - # in cancun only. Once this is approved and ported over to the - # the other forks in PR #890, this function has to be updated. - # This is a temporary change to make the tool work for cancun. - if self.fork.is_after_fork("ethereum.cancun"): - return self.fork.check_transaction( - self.alloc.state, - tx, - gas_available, - self.chain_id, - self.env.base_fee_per_gas, - self.env.excess_blob_gas, - ) - arguments = [tx] - - if self.fork.is_after_fork("ethereum.london"): - arguments.append(self.env.base_fee_per_gas) - - arguments.append(gas_available) - - if self.fork.is_after_fork("ethereum.spurious_dragon"): - arguments.append(self.chain_id) - - return self.fork.check_transaction(*arguments) - - def environment(self, tx: Any, gas_available: Any) -> Any: + def block_environment(self) -> Any: """ Create the environment for the transaction. The keyword arguments are adjusted according to the fork. @@ -179,102 +134,27 @@ def environment(self, tx: Any, gas_available: Any) -> Any: "block_hashes": self.env.block_hashes, "coinbase": self.env.coinbase, "number": self.env.block_number, - "gas_limit": self.env.block_gas_limit, "time": self.env.block_timestamp, "state": self.alloc.state, + "block_gas_limit": self.env.block_gas_limit, + "chain_id": self.chain_id, } + if self.fork.is_after_fork("ethereum.london"): + kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas + if self.fork.is_after_fork("ethereum.paris"): kw_arguments["prev_randao"] = self.env.prev_randao else: kw_arguments["difficulty"] = self.env.block_difficulty - if self.fork.is_after_fork("ethereum.istanbul"): - kw_arguments["chain_id"] = self.chain_id - - check_tx_return = self.check_transaction(tx, gas_available) - if self.fork.is_after_fork("ethereum.cancun"): - ( - sender_address, - effective_gas_price, - blob_versioned_hashes, - ) = check_tx_return - kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas - kw_arguments["caller"] = kw_arguments["origin"] = sender_address - kw_arguments["gas_price"] = effective_gas_price - kw_arguments["blob_versioned_hashes"] = blob_versioned_hashes + kw_arguments[ + "parent_beacon_block_root" + ] = self.env.parent_beacon_block_root kw_arguments["excess_blob_gas"] = self.env.excess_blob_gas - kw_arguments["transient_storage"] = self.fork.TransientStorage() - elif self.fork.is_after_fork("ethereum.london"): - sender_address, effective_gas_price = check_tx_return - kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas - kw_arguments["caller"] = kw_arguments["origin"] = sender_address - kw_arguments["gas_price"] = effective_gas_price - else: - sender_address = check_tx_return - kw_arguments["caller"] = kw_arguments["origin"] = sender_address - kw_arguments["gas_price"] = tx.gas_price - - kw_arguments["traces"] = [] - - return self.fork.Environment(**kw_arguments) - - def tx_trie_set(self, trie: Any, index: Any, tx: Any) -> Any: - """Add a transaction to the trie.""" - arguments = [trie, rlp.encode(Uint(index))] - if self.fork.is_after_fork("ethereum.berlin"): - arguments.append(self.fork.encode_transaction(tx)) - else: - arguments.append(tx) - - self.fork.trie_set(*arguments) - - def make_receipt( - self, tx: Any, process_transaction_return: Any, gas_available: Any - ) -> Any: - """Create a transaction receipt.""" - arguments = [tx] - - if self.fork.is_after_fork("ethereum.byzantium"): - arguments.append(process_transaction_return[2]) - else: - arguments.append(self.fork.state_root(self.alloc.state)) - arguments.append((self.env.block_gas_limit - gas_available)) - arguments.append(process_transaction_return[1]) - - return self.fork.make_receipt(*arguments) - - def pay_rewards(self) -> None: - """ - Pay the miner and the ommers. - This function is re-implemented since the uncle header - might not be available in the t8n tool. - """ - coinbase = self.env.coinbase - ommers = self.env.ommers - state = self.alloc.state - - miner_reward = self.BLOCK_REWARD + ( - U256(len(ommers)) * (self.BLOCK_REWARD // U256(32)) - ) - self.fork.create_ether(state, coinbase, miner_reward) - touched_accounts = [coinbase] - - for ommer in ommers: - # Ommer age with respect to the current block. - ommer_miner_reward = ((8 - ommer.delta) * self.BLOCK_REWARD) // 8 - self.fork.create_ether(state, ommer.address, ommer_miner_reward) - touched_accounts.append(ommer.address) - - if self.fork.is_after_fork("ethereum.spurious_dragon"): - # Destroy empty accounts that were touched by - # paying the rewards. This is only important if - # the block rewards were zero. - for account in touched_accounts: - if self.fork.account_exists_and_is_empty(state, account): - self.fork.destroy_account(state, account) + return self.fork.BlockEnvironment(**kw_arguments) def backup_state(self) -> None: """Back up the state in order to restore in case of an error.""" @@ -292,195 +172,94 @@ def restore_state(self) -> None: state = self.alloc.state state._main_trie, state._storage_tries = self.alloc.state_backup - def apply_body(self) -> None: + def run_state_test(self) -> Any: """ - The apply body function is seen as the entry point of - the t8n tool into the designated fork. The function has been - re-implemented here to account for the differences in the - transaction processing between the forks. However, the general - structure of the function is the same. + Apply a single transaction on pre-state. No system operations + are performed. """ - block_gas_limit = self.env.block_gas_limit - - gas_available = block_gas_limit - transactions_trie = self.fork.Trie(secured=False, default=None) - receipts_trie = self.fork.Trie(secured=False, default=None) - block_logs = () - blob_gas_used = Uint(0) - if ( - self.fork.is_after_fork("ethereum.prague") - and not self.options.state_test - ): - deposit_requests: Bytes = b"" + block_env = self.block_environment() + block_output = self.fork.BlockOutput() + self.backup_state() + if len(self.txs.transactions) > 0: + tx = self.txs.transactions[0] + try: + self.fork.process_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + index=Uint(0), + ) + except EthereumException as e: + self.txs.rejected_txs[0] = f"Failed transaction: {e!r}" + self.restore_state() + self.logger.warning(f"Transaction {0} failed: {str(e)}") + + self.result.update(self, block_env, block_output) + self.result.rejected = self.txs.rejected_txs + def run_blockchain_test(self) -> None: + """ + Apply a block on the pre-state. Also includes system operations. + """ + block_env = self.block_environment() + block_output = self.fork.BlockOutput() + + if self.fork.is_after_fork("ethereum.prague"): self.fork.process_system_transaction( - self.fork.HISTORY_STORAGE_ADDRESS, - self.env.parent_hash, - self.env.block_hashes, - self.env.coinbase, - self.env.block_number, - self.env.base_fee_per_gas, - self.env.block_gas_limit, - self.env.block_timestamp, - self.env.prev_randao, - self.alloc.state, - self.chain_id, - self.env.excess_blob_gas, + block_env=block_env, + target_address=self.fork.HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash ) - if ( - self.fork.is_after_fork("ethereum.cancun") - and not self.options.state_test - ): + if self.fork.is_after_fork("ethereum.cancun"): self.fork.process_system_transaction( - self.fork.BEACON_ROOTS_ADDRESS, - self.env.parent_beacon_block_root, - self.env.block_hashes, - self.env.coinbase, - self.env.block_number, - self.env.base_fee_per_gas, - self.env.block_gas_limit, - self.env.block_timestamp, - self.env.prev_randao, - self.alloc.state, - self.chain_id, - self.env.excess_blob_gas, + block_env=block_env, + target_address=self.fork.BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, ) - for i, (tx_idx, tx) in enumerate(self.txs.transactions): - # i is the index among valid transactions - # tx_idx is the index among all transactions. tx_idx is only used - # to identify the transaction in the rejected_txs dictionary. + for i, tx in zip(self.txs.successfully_parsed, self.txs.transactions): self.backup_state() - try: - env = self.environment(tx, gas_available) - - process_transaction_return = self.fork.process_transaction( - env, tx + self.fork.process_transaction( + block_env, block_output, tx, Uint(i) ) - - if self.fork.is_after_fork("ethereum.cancun"): - blob_gas_used += self.fork.calculate_total_blob_gas(tx) - if blob_gas_used > self.fork.MAX_BLOB_GAS_PER_BLOCK: - raise InvalidBlock except EthereumException as e: - # The tf tools expects some non-blank error message - # even in case e is blank. - self.txs.rejected_txs[tx_idx] = f"Failed transaction: {e!r}" + self.txs.rejected_txs[i] = f"Failed transaction: {e!r}" self.restore_state() - self.logger.warning(f"Transaction {tx_idx} failed: {e!r}") - else: - self.txs.add_transaction(tx) - gas_consumed = process_transaction_return[0] - gas_available -= gas_consumed - - if self.options.trace: - tx_hash = self.txs.get_tx_hash(tx) - output_traces( - env.traces, i, tx_hash, self.options.output_basedir - ) - self.tx_trie_set(transactions_trie, i, tx) - - receipt = self.make_receipt( - tx, process_transaction_return, gas_available - ) - - self.fork.trie_set( - receipts_trie, - rlp.encode(Uint(i)), - receipt, - ) - if ( - self.fork.is_after_fork("ethereum.prague") - and not self.options.state_test - ): - deposit_requests += ( - self.fork.parse_deposit_requests_from_receipt(receipt) - ) - - self.txs.add_receipt(tx, gas_consumed) - - block_logs += process_transaction_return[1] - - self.alloc.state._snapshots = [] - - if self.BLOCK_REWARD is not None: - self.pay_rewards() - - block_gas_used = block_gas_limit - gas_available - - block_logs_bloom = self.fork.logs_bloom(block_logs) - - logs_hash = keccak256(rlp.encode(block_logs)) - - if ( - self.fork.is_after_fork("ethereum.shanghai") - and not self.options.state_test - ): - withdrawals_trie = self.fork.Trie(secured=False, default=None) - - for i, wd in enumerate(self.env.withdrawals): - self.fork.trie_set( - withdrawals_trie, rlp.encode(Uint(i)), rlp.encode(wd) - ) - - self.fork.process_withdrawal(self.alloc.state, wd) - - if self.fork.account_exists_and_is_empty( - self.alloc.state, wd.address - ): - self.fork.destroy_account(self.alloc.state, wd.address) - - self.result.withdrawals_root = self.fork.root(withdrawals_trie) - - if self.fork.is_after_fork("ethereum.cancun"): - self.result.blob_gas_used = blob_gas_used - self.result.excess_blob_gas = self.env.excess_blob_gas - - if ( - self.fork.is_after_fork("ethereum.prague") - and not self.options.state_test - ): - requests_from_execution = ( - self.fork.process_general_purpose_requests( - deposit_requests, - self.alloc.state, - self.env.block_hashes, - self.env.coinbase, - self.env.block_number, - self.env.base_fee_per_gas, - self.env.block_gas_limit, - self.env.block_timestamp, - self.env.prev_randao, - self.chain_id, - self.env.excess_blob_gas, - ) + self.logger.warning(f"Transaction {i} failed: {e!r}") + + if not self.fork.is_after_fork("ethereum.paris"): + self.fork.pay_rewards( + block_env.state, + block_env.number, + block_env.coinbase, + self.env.ommers, ) - requests_hash = self.fork.compute_requests_hash( - requests_from_execution + + if self.fork.is_after_fork("ethereum.shanghai"): + self.fork.process_withdrawals( + block_env, block_output, self.env.withdrawals ) - self.result.state_root = self.fork.state_root(self.alloc.state) - self.result.tx_root = self.fork.root(transactions_trie) - self.result.receipt_root = self.fork.root(receipts_trie) - self.result.bloom = block_logs_bloom - self.result.logs_hash = logs_hash - self.result.rejected = self.txs.rejected_txs - self.result.receipts = self.txs.successful_receipts - self.result.gas_used = block_gas_used + if self.fork.is_after_fork("ethereum.prague"): + self.fork.process_general_purpose_requests(block_env, block_output) - if ( - self.fork.is_after_fork("ethereum.prague") - and not self.options.state_test - ): - self.result.requests_hash = requests_hash - self.result.requests = requests_from_execution + self.result.update(self, block_env, block_output) + self.result.rejected = self.txs.rejected_txs def run(self) -> int: """Run the transition and provide the relevant outputs""" + # Clean out files from the output directory + for file in os.listdir(self.options.output_basedir): + if file.endswith(".json") or file.endswith(".jsonl"): + os.remove(os.path.join(self.options.output_basedir, file)) + try: - self.apply_body() + if self.options.state_test: + self.run_state_test() + else: + self.run_blockchain_test() except FatalException as e: self.logger.error(str(e)) return 1 diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py index 648ed089af..ab0603c6b4 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py @@ -6,7 +6,15 @@ import os from contextlib import ExitStack from dataclasses import asdict, dataclass, is_dataclass -from typing import List, Optional, Protocol, TextIO, Union, runtime_checkable +from typing import ( + Any, + List, + Optional, + Protocol, + TextIO, + Union, + runtime_checkable, +) from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint @@ -26,6 +34,7 @@ ) EXCLUDE_FROM_OUTPUT = ["gasCostTraced", "errorTraced", "precompile"] +OUTPUT_DIR = "." @dataclass @@ -71,11 +80,13 @@ def __init__( @runtime_checkable -class Environment(Protocol): +class TransactionEnvironment(Protocol): """ - The class implements the environment interface for trace. + The class implements the tx_env interface for trace. """ + index_in_block: Optional[Uint] + tx_hash: Optional[Bytes] traces: List[Union["Trace", "FinalTrace"]] @@ -86,6 +97,7 @@ class Message(Protocol): """ depth: int + tx_env: TransactionEnvironment parent_evm: Optional["Evm"] @@ -100,7 +112,6 @@ class EvmWithoutReturnData(Protocol): memory: bytearray code: Bytes gas_left: Uint - env: Environment refund_counter: int running: bool message: Message @@ -117,7 +128,6 @@ class EvmWithReturnData(Protocol): memory: bytearray code: Bytes gas_left: Uint - env: Environment refund_counter: int running: bool message: Message @@ -128,7 +138,7 @@ class EvmWithReturnData(Protocol): def evm_trace( - evm: object, + evm: Any, event: TraceEvent, trace_memory: bool = False, trace_stack: bool = True, @@ -137,11 +147,19 @@ def evm_trace( """ Create a trace of the event. """ + # System Transaction do not have a tx_hash or index + if ( + evm.message.tx_env.index_in_block is None + or evm.message.tx_env.tx_hash is None + ): + return + assert isinstance(evm, (EvmWithoutReturnData, EvmWithReturnData)) + traces = evm.message.tx_env.traces last_trace = None - if evm.env.traces: - last_trace = evm.env.traces[-1] + if traces: + last_trace = traces[-1] refund_counter = evm.refund_counter parent_evm = evm.message.parent_evm @@ -167,7 +185,13 @@ def evm_trace( pass elif isinstance(event, TransactionEnd): final_trace = FinalTrace(event.gas_used, event.output, event.error) - evm.env.traces.append(final_trace) + traces.append(final_trace) + + output_traces( + traces, + evm.message.tx_env.index_in_block, + evm.message.tx_env.tx_hash, + ) elif isinstance(event, PrecompileStart): new_trace = Trace( pc=int(evm.pc), @@ -184,7 +208,7 @@ def evm_trace( precompile=True, ) - evm.env.traces.append(new_trace) + traces.append(new_trace) elif isinstance(event, PrecompileEnd): assert isinstance(last_trace, Trace) @@ -208,7 +232,7 @@ def evm_trace( opName=str(event.op).split(".")[-1], ) - evm.env.traces.append(new_trace) + traces.append(new_trace) elif isinstance(event, OpEnd): assert isinstance(last_trace, Trace) @@ -253,7 +277,7 @@ def evm_trace( error=type(event.error).__name__, ) - evm.env.traces.append(new_trace) + traces.append(new_trace) elif not last_trace.errorTraced: # If the error for the last trace is not covered # the exception is attributed to the last trace. @@ -273,7 +297,7 @@ def evm_trace( trace_return_data, ) elif isinstance(event, GasAndRefund): - if not evm.env.traces: + if len(traces) == 0: # In contract creation transactions, there may not be any traces return @@ -325,9 +349,8 @@ def output_op_trace( def output_traces( traces: List[Union[Trace, FinalTrace]], - tx_index: int, + index_in_block: int, tx_hash: bytes, - output_basedir: str | TextIO = ".", ) -> None: """ Output the traces to a json file. @@ -335,15 +358,15 @@ def output_traces( with ExitStack() as stack: json_file: TextIO - if isinstance(output_basedir, str): + if isinstance(OUTPUT_DIR, str): tx_hash_str = "0x" + tx_hash.hex() output_path = os.path.join( - output_basedir, f"trace-{tx_index}-{tx_hash_str}.jsonl" + OUTPUT_DIR, f"trace-{index_in_block}-{tx_hash_str}.jsonl" ) json_file = open(output_path, "w") stack.push(json_file) else: - json_file = output_basedir + json_file = OUTPUT_DIR for trace in traces: if getattr(trace, "precompile", False): diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index d5f0f67284..108e41d09c 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -3,9 +3,9 @@ """ import json from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple -from ethereum_rlp import rlp +from ethereum_rlp import Simple, rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint @@ -85,19 +85,11 @@ class Txs: return a list of transactions. """ - rejected_txs: Dict[int, str] - successful_txs: List[Any] - successful_receipts: List[Any] - all_txs: List[Any] - t8n: "T8N" - data: Any - rlp_input: bool - def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): self.t8n = t8n + self.successfully_parsed: List[int] = [] + self.transactions: List[Tuple[Uint, Any]] = [] self.rejected_txs = {} - self.successful_txs = [] - self.successful_receipts = [] self.rlp_input = False self.all_txs = [] @@ -109,25 +101,21 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): data = json.load(f) if data is None: - self.data = [] + self.data: Simple = [] elif isinstance(data, str): self.rlp_input = True self.data = rlp.decode(hex_to_bytes(data)) else: self.data = data - @property - def transactions(self) -> Iterator[Tuple[int, Any]]: - """ - Read the transactions file and return a list of transactions. - Can read from JSON or RLP. - """ for idx, raw_tx in enumerate(self.data): try: if self.rlp_input: - yield idx, self.parse_rlp_tx(raw_tx) + self.transactions.append(self.parse_rlp_tx(raw_tx)) + self.successfully_parsed.append(idx) else: - yield idx, self.parse_json_tx(raw_tx) + self.transactions.append(self.parse_json_tx(raw_tx)) + self.successfully_parsed.append(idx) except UnsupportedTx as e: self.t8n.logger.warning( f"Unsupported transaction type {idx}: " @@ -199,38 +187,6 @@ def parse_json_tx(self, raw_tx: Any) -> Any: return transaction - def add_transaction(self, tx: Any) -> None: - """ - Add a transaction to the list of successful transactions. - """ - if self.t8n.fork.is_after_fork("ethereum.berlin"): - self.successful_txs.append(self.t8n.fork.encode_transaction(tx)) - else: - self.successful_txs.append(tx) - - def get_tx_hash(self, tx: Any) -> bytes: - """ - Get the transaction hash of a transaction. - """ - if self.t8n.fork.is_after_fork("ethereum.berlin") and not isinstance( - tx, self.t8n.fork.LegacyTransaction - ): - return keccak256(self.t8n.fork.encode_transaction(tx)) - else: - return keccak256(rlp.encode(tx)) - - def add_receipt(self, tx: Any, gas_consumed: Uint) -> None: - """ - Add t8n receipt info for valid tx - """ - tx_hash = self.get_tx_hash(tx) - - data = { - "transactionHash": "0x" + tx_hash.hex(), - "gasUsed": hex(gas_consumed), - } - self.successful_receipts.append(data) - def sign_transaction(self, json_tx: Any) -> None: """ Sign a transaction. This function will be invoked if a `secretKey` @@ -312,6 +268,68 @@ class Result: requests_hash: Optional[Hash32] = None requests: Optional[List[Bytes]] = None + def get_receipts_from_tries( + self, t8n: Any, tx_trie: Any, receipts_trie: Any + ) -> List[Any]: + """ + Get receipts from the transaction and receipts tries. + """ + receipts: List[Any] = [] + for index in tx_trie._data: + if index not in receipts_trie._data: + # Meaning the transaction has somehow failed + return receipts + + tx = tx_trie._data.get(index) + tx_hash = t8n.fork.get_transaction_hash(tx) + + receipt = receipts_trie._data.get(index) + + if hasattr(t8n.fork, "decode_receipt"): + decoded_receipt = t8n.fork.decode_receipt(receipt) + else: + decoded_receipt = receipt + + gas_consumed = decoded_receipt.cumulative_gas_used + + receipts.append( + { + "transactionHash": "0x" + tx_hash.hex(), + "gasUsed": hex(gas_consumed), + } + ) + + return receipts + + def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None: + """ + Update the result after processing the inputs. + """ + self.gas_used = block_output.block_gas_used + self.tx_root = t8n.fork.root(block_output.transactions_trie) + self.receipt_root = t8n.fork.root(block_output.receipts_trie) + self.bloom = t8n.fork.logs_bloom(block_output.block_logs) + self.logs_hash = keccak256(rlp.encode(block_output.block_logs)) + self.state_root = t8n.fork.state_root(block_env.state) + self.receipts = self.get_receipts_from_tries( + t8n, block_output.transactions_trie, block_output.receipts_trie + ) + + if hasattr(block_env, "base_fee_per_gas"): + self.base_fee = block_env.base_fee_per_gas + + if hasattr(block_output, "withdrawals_trie"): + self.withdrawals_root = t8n.fork.root( + block_output.withdrawals_trie + ) + + if hasattr(block_env, "excess_blob_gas"): + self.excess_blob_gas = block_env.excess_blob_gas + + if hasattr(block_output, "requests"): + self.requests = block_output.requests + self.requests_hash = t8n.fork.compute_requests_hash(self.requests) + def to_json(self) -> Any: """Encode the result to JSON""" data = {} @@ -345,13 +363,7 @@ def to_json(self) -> Any: for idx, error in self.rejected.items() ] - data["receipts"] = [ - { - "transactionHash": item["transactionHash"], - "gasUsed": item["gasUsed"], - } - for item in self.receipts - ] + data["receipts"] = self.receipts if self.requests_hash is not None: assert self.requests is not None diff --git a/tests/berlin/test_evm_tools.py b/tests/berlin/test_evm_tools.py index 8589da292f..8735219cde 100644 --- a/tests/berlin/test_evm_tools.py +++ b/tests/berlin/test_evm_tools.py @@ -3,21 +3,18 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" -FORK_NAME = "Berlin" - -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, +ETHEREUM_STATE_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Berlin" SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -28,15 +25,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/berlin/test_state_transition.py b/tests/berlin/test_state_transition.py index 48f947aec5..b5d935463f 100644 --- a/tests/berlin/test_state_transition.py +++ b/tests/berlin/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,18 +11,12 @@ run_blockchain_st_test, ) -fetch_berlin_tests = partial(fetch_state_test_files, network="Berlin") - -FIXTURES_LOADER = Load("Berlin", "berlin") - -run_berlin_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Berlin" +PACKAGE = "berlin" # Every test below takes more than 60s to run and # hence they've been marked as slow @@ -73,107 +62,35 @@ "stTimeConsuming/", ) -fetch_state_tests = partial( - fetch_berlin_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_general_state_tests(test_case: Dict) -> None: - run_berlin_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.LegacyTransaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - chain_id=Uint(1), - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/byzantium/test_evm_tools.py b/tests/byzantium/test_evm_tools.py index f14fe49df4..cdf5ea6a22 100644 --- a/tests/byzantium/test_evm_tools.py +++ b/tests/byzantium/test_evm_tools.py @@ -3,33 +3,51 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "Byzantium" -run_evm_tools_test = partial( +SLOW_TESTS = () + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/byzantium/test_state_transition.py b/tests/byzantium/test_state_transition.py index c392873f78..dd15c234ab 100644 --- a/tests/byzantium/test_state_transition.py +++ b/tests/byzantium/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,18 +11,12 @@ run_blockchain_st_test, ) -fetch_byzantium_tests = partial(fetch_state_test_files, network="Byzantium") - -FIXTURES_LOADER = Load("Byzantium", "byzantium") - -run_byzantium_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Byzantium" +PACKAGE = "byzantium" # These are tests that are considered to be incorrect, # Please provide an explanation when adding entries @@ -67,124 +56,35 @@ "bcUncleHeaderValidity/wrongMixHash.json", ) -fetch_legacy_state_tests = partial( - fetch_byzantium_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=LEGACY_SLOW_TESTS, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_byzantium_blockchain_st_tests(test_case) - - -# Run Non-Legacy StateTests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_byzantium_tests(test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_state_tests(test_case: Dict) -> None: - run_byzantium_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/cancun/test_evm_tools.py b/tests/cancun/test_evm_tools.py index fe78ba037f..3bd6c91a6f 100644 --- a/tests/cancun/test_evm_tools.py +++ b/tests/cancun/test_evm_tools.py @@ -3,22 +3,17 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +ETHEREUM_STATE_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "Cancun" -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, -) - SLOW_TESTS = ( "CALLBlake2f_MaxRounds", "CALLCODEBlake2f", @@ -28,15 +23,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/cancun/test_state_transition.py b/tests/cancun/test_state_transition.py index e8544bd186..6c72238bb4 100644 --- a/tests/cancun/test_state_transition.py +++ b/tests/cancun/test_state_transition.py @@ -3,7 +3,7 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -11,22 +11,10 @@ run_blockchain_st_test, ) -fetch_cancun_tests = partial(fetch_state_test_files, network="Cancun") - -FIXTURES_LOADER = Load("Cancun", "cancun") - -run_cancun_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER -) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -ETHEREUM_SPEC_TESTS_PATH = TEST_FIXTURES["execution_spec_tests"][ - "fixture_path" -] - - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +ETHEREUM_BLOCKCHAIN_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Cancun" +PACKAGE = "cancun" SLOW_TESTS = ( # GeneralStateTests @@ -74,32 +62,35 @@ "stStaticCall/", ) -fetch_state_tests = partial( - fetch_cancun_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_general_state_tests(test_case: Dict) -> None: - run_cancun_blockchain_st_tests(test_case) - - -# Run execution-spec-generated-tests -test_dir = f"{ETHEREUM_SPEC_TESTS_PATH}/fixtures/withdrawals" +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_cancun_tests(test_dir), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_execution_specs_generated_tests(test_case: Dict) -> None: - run_cancun_blockchain_st_tests(test_case) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/conftest.py b/tests/conftest.py index 0f64dc1704..d61805f4b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,15 @@ def pytest_addoption(parser: Parser) -> None: help="Use optimized state and ethash", ) + parser.addoption( + "--evm_trace", + dest="evm_trace", + default=False, + action="store_const", + const=True, + help="Create an evm trace", + ) + def pytest_configure(config: Config) -> None: """ @@ -37,6 +46,19 @@ def pytest_configure(config: Config) -> None: ethereum_optimized.monkey_patch(None) + if config.getoption("evm_trace"): + path = config.getoption("evm_trace") + import ethereum.trace + import ethereum_spec_tools.evm_tools.t8n.evm_trace as evm_trace_module + from ethereum_spec_tools.evm_tools.t8n.evm_trace import ( + evm_trace as new_trace_function, + ) + + # Replace the function in the module + ethereum.trace.evm_trace = new_trace_function + # Set the output directory for traces + evm_trace_module.OUTPUT_DIR = path + def download_fixtures(url: str, location: str) -> None: # xdist processes will all try to download the fixtures. diff --git a/tests/constantinople/test_evm_tools.py b/tests/constantinople/test_evm_tools.py index 01536adfdc..0cafe1c7db 100644 --- a/tests/constantinople/test_evm_tools.py +++ b/tests/constantinople/test_evm_tools.py @@ -3,33 +3,50 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "ConstantinopleFix" -run_evm_tools_test = partial( +SLOW_TESTS = () +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/constantinople/test_state_transition.py b/tests/constantinople/test_state_transition.py index 1eda69979c..a6e8ae089e 100644 --- a/tests/constantinople/test_state_transition.py +++ b/tests/constantinople/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,20 +11,12 @@ run_blockchain_st_test, ) -fetch_constantinople_tests = partial( - fetch_state_test_files, network="ConstantinopleFix" +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) - -FIXTURES_LOADER = Load("ConstantinopleFix", "constantinople") - -run_constantinople_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER -) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "ConstantinopleFix" +PACKAGE = "constantinople" # These are tests that are considered to be incorrect, @@ -70,124 +57,35 @@ "bcUncleHeaderValidity/wrongMixHash.json", ) -fetch_legacy_state_tests = partial( - fetch_constantinople_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=LEGACY_SLOW_TESTS, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_constantinople_blockchain_st_tests(test_case) - - -# Run Non-Legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_constantinople_tests(test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_state_tests(test_case: Dict) -> None: - run_constantinople_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/frontier/test_evm_tools.py b/tests/frontier/test_evm_tools.py index fb6635dea0..7ef3de2ac3 100644 --- a/tests/frontier/test_evm_tools.py +++ b/tests/frontier/test_evm_tools.py @@ -3,33 +3,51 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "Frontier" -run_evm_tools_test = partial( +SLOW_TESTS = () + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/frontier/test_state_transition.py b/tests/frontier/test_state_transition.py index 840fe3a9ca..1f27ff15f9 100644 --- a/tests/frontier/test_state_transition.py +++ b/tests/frontier/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,21 +11,12 @@ run_blockchain_st_test, ) -fetch_frontier_tests = partial(fetch_state_test_files, network="Frontier") - -FIXTURES_LOADER = Load("Frontier", "frontier") - -run_frontier_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER -) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - - -# Run legacy general state tests -legacy_test_dir = ( +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Frontier" +PACKAGE = "frontier" LEGACY_IGNORE_LIST = ( # Valid block tests to be ignored @@ -59,126 +45,35 @@ "bcUncleHeaderValidity/wrongMixHash.json", ) -fetch_legacy_state_tests = partial( - fetch_frontier_tests, - legacy_test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=SLOW_LIST, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_frontier_blockchain_st_tests(test_case) - - -# Run Non-Legacy Tests -non_legacy_test_dir = ( - f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" -) - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_frontier_tests(non_legacy_test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_tests(test_case: Dict) -> None: - run_frontier_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index b551825535..b25e2049ff 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -1,10 +1,6 @@ # Update the links and commit has in order to consume # newer/other tests TEST_FIXTURES = { - "execution_spec_tests": { - "url": "https://github.com/ethereum/execution-spec-tests/releases/download/v0.2.5/fixtures.tar.gz", - "fixture_path": "tests/fixtures/execution_spec_tests", - }, "evm_tools_testdata": { "url": "https://github.com/gurukamath/evm-tools-testdata.git", "commit_hash": "792422d", @@ -17,7 +13,11 @@ }, "latest_fork_tests": { "url": "https://github.com/gurukamath/latest_fork_tests.git", - "commit_hash": "e15efcb", + "commit_hash": "bc74af5", "fixture_path": "tests/fixtures/latest_fork_tests", }, } + + +ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] +EEST_TESTS_PATH = TEST_FIXTURES["latest_fork_tests"]["fixture_path"] diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index 82ff9abe37..38af0eafff 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -127,6 +127,6 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: t8n_options = parser.parse_args(t8n_args) t8n = T8N(t8n_options, sys.stdout, in_stream) - t8n.apply_body() + t8n.run_state_test() assert hex_to_bytes(post_hash) == t8n.result.state_root diff --git a/tests/helpers/load_vm_tests.py b/tests/helpers/load_vm_tests.py index 6469f58e3d..361c82bd83 100644 --- a/tests/helpers/load_vm_tests.py +++ b/tests/helpers/load_vm_tests.py @@ -40,6 +40,9 @@ def __init__(self, network: str, fork_name: str): self.Account = self.fork_types.Account self.Address = self.fork_types.Address + self.transactions = self._module("transactions") + self.Transaction = self.transactions.Transaction + self.hexadecimal = self._module("utils.hexadecimal") self.hex_to_address = self.hexadecimal.hex_to_address @@ -47,7 +50,8 @@ def __init__(self, network: str, fork_name: str): self.prepare_message = self.message.prepare_message self.vm = self._module("vm") - self.Environment = self.vm.Environment + self.BlockEnvironment = self.vm.BlockEnvironment + self.TransactionEnvironment = self.vm.TransactionEnvironment self.interpreter = self._module("vm.interpreter") self.process_message_call = self.interpreter.process_message_call @@ -62,18 +66,17 @@ def run_test( Execute a test case and check its post state. """ test_data = self.load_test(test_dir, test_file) - target = test_data["target"] - env = test_data["env"] + block_env = test_data["block_env"] + tx_env = test_data["tx_env"] + tx = test_data["tx"] + message = self.prepare_message( - caller=test_data["caller"], - target=target, - value=test_data["value"], - data=test_data["data"], - gas=test_data["gas"], - env=env, + block_env=block_env, + tx_env=tx_env, + tx=tx, ) - output = self.process_message_call(message, env) + output = self.process_message_call(message) if test_data["has_post_state"]: if check_gas_left: @@ -89,10 +92,10 @@ def run_test( for addr in test_data["post_state_addresses"]: assert self.storage_root( test_data["expected_post_state"], addr - ) == self.storage_root(env.state, addr) + ) == self.storage_root(block_env.state, addr) else: assert output.error is not None - self.close_state(env.state) + self.close_state(block_env.state) self.close_state(test_data["expected_post_state"]) def load_test(self, test_dir: str, test_file: str) -> Any: @@ -104,16 +107,33 @@ def load_test(self, test_dir: str, test_file: str) -> Any: with open(path, "r") as fp: json_data = json.load(fp)[test_name] - env = self.json_to_env(json_data) + block_env = self.json_to_block_env(json_data) + + tx = self.Transaction( + nonce=U256(0), + gas_price=hex_to_u256(json_data["exec"]["gasPrice"]), + gas=hex_to_uint(json_data["exec"]["gas"]), + to=self.hex_to_address(json_data["exec"]["address"]), + value=hex_to_u256(json_data["exec"]["value"]), + data=hex_to_bytes(json_data["exec"]["data"]), + v=U256(0), + r=U256(0), + s=U256(0), + ) + + tx_env = self.TransactionEnvironment( + origin=self.hex_to_address(json_data["exec"]["caller"]), + gas_price=tx.gas_price, + gas=tx.gas, + index_in_block=Uint(0), + tx_hash=b"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + traces=[], + ) return { - "caller": self.hex_to_address(json_data["exec"]["caller"]), - "target": self.hex_to_address(json_data["exec"]["address"]), - "data": hex_to_bytes(json_data["exec"]["data"]), - "value": hex_to_u256(json_data["exec"]["value"]), - "gas": hex_to_uint(json_data["exec"]["gas"]), - "depth": Uint(0), - "env": env, + "block_env": block_env, + "tx_env": tx_env, + "tx": tx, "expected_gas_left": hex_to_u256(json_data.get("gas", "0x64")), "expected_logs_hash": hex_to_bytes(json_data.get("logs", "0x00")), "expected_post_state": self.json_to_state( @@ -125,9 +145,9 @@ def load_test(self, test_dir: str, test_file: str) -> Any: "has_post_state": bool(json_data.get("post", {})), } - def json_to_env(self, json_data: Any) -> Any: + def json_to_block_env(self, json_data: Any) -> Any: """ - Deserialize an `Environment` instance from JSON. + Deserialize a `BlockEnvironment` instance from JSON. """ caller_hex_address = json_data["exec"]["caller"] # Some tests don't have the caller state defined in the test case. Hence @@ -146,18 +166,15 @@ def json_to_env(self, json_data: Any) -> Any: chain_id=U64(1), ) - return self.Environment( - caller=self.hex_to_address(json_data["exec"]["caller"]), - origin=self.hex_to_address(json_data["exec"]["origin"]), + return self.BlockEnvironment( + chain_id=chain.chain_id, + state=current_state, block_hashes=self.get_last_256_block_hashes(chain), coinbase=self.hex_to_address(json_data["env"]["currentCoinbase"]), number=hex_to_uint(json_data["env"]["currentNumber"]), - gas_limit=hex_to_uint(json_data["env"]["currentGasLimit"]), - gas_price=hex_to_u256(json_data["exec"]["gasPrice"]), + block_gas_limit=hex_to_uint(json_data["env"]["currentGasLimit"]), time=hex_to_u256(json_data["env"]["currentTimestamp"]), difficulty=hex_to_uint(json_data["env"]["currentDifficulty"]), - state=current_state, - traces=[], ) def json_to_state(self, raw: Any) -> Any: diff --git a/tests/homestead/test_evm_tools.py b/tests/homestead/test_evm_tools.py index 7bb7e32bdc..e8e2e24f52 100644 --- a/tests/homestead/test_evm_tools.py +++ b/tests/homestead/test_evm_tools.py @@ -3,33 +3,52 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "Homestead" -run_evm_tools_test = partial( + +SLOW_TESTS = () + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/homestead/test_state_transition.py b/tests/homestead/test_state_transition.py index 978ee95512..4fd6613646 100644 --- a/tests/homestead/test_state_transition.py +++ b/tests/homestead/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,19 +11,12 @@ run_blockchain_st_test, ) -fetch_homestead_tests = partial(fetch_state_test_files, network="Homestead") - -FIXTURES_LOADER = Load("Homestead", "homestead") - -run_homestead_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - - -# Run legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Homestead" +PACKAGE = "homestead" # Every test below takes more than 60s to run and # hence they've been marked as slow @@ -143,124 +131,35 @@ "randomStatetest94_", ) -fetch_legacy_state_tests = partial( - fetch_homestead_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=LEGACY_SLOW_TESTS, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_homestead_blockchain_st_tests(test_case) - - -# Run Non-Legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_homestead_tests(test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_tests(test_case: Dict) -> None: - run_homestead_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/istanbul/test_evm_tools.py b/tests/istanbul/test_evm_tools.py index 279083cbb3..80c169a440 100644 --- a/tests/istanbul/test_evm_tools.py +++ b/tests/istanbul/test_evm_tools.py @@ -3,21 +3,18 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" -FORK_NAME = "Istanbul" - -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, +ETHEREUM_STATE_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Istanbul" SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -28,15 +25,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/istanbul/test_state_transition.py b/tests/istanbul/test_state_transition.py index b778d25c14..50fada9659 100644 --- a/tests/istanbul/test_state_transition.py +++ b/tests/istanbul/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,18 +11,12 @@ run_blockchain_st_test, ) -fetch_istanbul_tests = partial(fetch_state_test_files, network="Istanbul") - -FIXTURES_LOADER = Load("Istanbul", "istanbul") - -run_istanbul_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Istanbul" +PACKAGE = "istanbul" # Every test below takes more than 60s to run and # hence they've been marked as slow @@ -75,107 +64,35 @@ "stTimeConsuming/", ) -fetch_state_tests = partial( - fetch_istanbul_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_state_tests(test_case: Dict) -> None: - run_istanbul_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - chain_id=Uint(1), - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/london/test_evm_tools.py b/tests/london/test_evm_tools.py index 0e0b8aa1c5..0347da6cec 100644 --- a/tests/london/test_evm_tools.py +++ b/tests/london/test_evm_tools.py @@ -3,21 +3,18 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" -FORK_NAME = "London" - -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, +ETHEREUM_STATE_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "London" SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -28,15 +25,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/london/test_state_transition.py b/tests/london/test_state_transition.py index 87723c8130..764ee942de 100644 --- a/tests/london/test_state_transition.py +++ b/tests/london/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,18 +11,12 @@ run_blockchain_st_test, ) -fetch_london_tests = partial(fetch_state_test_files, network="London") - -FIXTURES_LOADER = Load("London", "london") - -run_london_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "London" +PACKAGE = "london" # Every test below takes more than 60s to run and # hence they've been marked as slow @@ -75,109 +64,35 @@ "stTimeConsuming/", ) -fetch_state_tests = partial( - fetch_london_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_state_tests(test_case: Dict) -> None: - run_london_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - base_fee_per_gas=Uint(16), - ) - - genesis_header_hash = bytes.fromhex( - "4a62c29ca7f3a61e5519eabbf57a40bb28ee1f164839b3160281c30d2443a69e" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.LegacyTransaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - chain_id=Uint(1), - base_fee_per_gas=Uint(16), - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/paris/test_evm_tools.py b/tests/paris/test_evm_tools.py index 18313a7584..1598e44b4b 100644 --- a/tests/paris/test_evm_tools.py +++ b/tests/paris/test_evm_tools.py @@ -3,21 +3,18 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" -FORK_NAME = "Paris" - -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, +ETHEREUM_STATE_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Paris" SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -28,15 +25,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/paris/test_state_transition.py b/tests/paris/test_state_transition.py index 46c47ae95c..5478389ca6 100644 --- a/tests/paris/test_state_transition.py +++ b/tests/paris/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,18 +11,12 @@ run_blockchain_st_test, ) -fetch_paris_tests = partial(fetch_state_test_files, network="Paris") - -FIXTURES_LOADER = Load("Paris", "paris") - -run_paris_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Paris" +PACKAGE = "paris" # Every test below takes more than 60s to run and # hence they've been marked as slow @@ -75,109 +64,35 @@ "stTimeConsuming/", ) -fetch_state_tests = partial( - fetch_paris_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_state_tests(test_case: Dict) -> None: - run_paris_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - prev_randao=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - base_fee_per_gas=Uint(16), - ) - - genesis_header_hash = bytes.fromhex( - "4a62c29ca7f3a61e5519eabbf57a40bb28ee1f164839b3160281c30d2443a69e" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.LegacyTransaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - prev_randao=U256.from_be_bytes(genesis_block.header.prev_randao), - state=state, - chain_id=Uint(1), - base_fee_per_gas=Uint(16), - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/prague/test_evm_tools.py b/tests/prague/test_evm_tools.py index 3879ee1d65..93770bf686 100644 --- a/tests/prague/test_evm_tools.py +++ b/tests/prague/test_evm_tools.py @@ -1,23 +1,19 @@ from functools import partial -from typing import Dict, Generator, Tuple +from typing import Dict import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +ETHEREUM_STATE_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "Prague" -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, -) SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -34,27 +30,37 @@ "tests/prague/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-multi_inf_pair-]", ) -test_dirs = ( - "tests/fixtures/latest_fork_tests/state_tests/prague/eip2537_bls_12_381_precompiles", - "tests/fixtures/latest_fork_tests/state_tests/prague/eip7702_set_code_tx", - "tests/fixtures/latest_fork_tests/state_tests/prague/eip7623_increase_calldata_cost", + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, ) -def fetch_temporary_tests(test_dirs: Tuple[str, ...]) -> Generator: - for test_dir in test_dirs: - yield from fetch_evm_tools_tests( - test_dir, - FORK_NAME, - SLOW_TESTS, - ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_temporary_tests(test_dirs), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/prague/test_state_transition.py b/tests/prague/test_state_transition.py index f47d9f6a66..dd2bd52b43 100644 --- a/tests/prague/test_state_transition.py +++ b/tests/prague/test_state_transition.py @@ -1,9 +1,9 @@ from functools import partial -from typing import Dict, Generator, Tuple +from typing import Dict import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -11,22 +11,10 @@ run_blockchain_st_test, ) -fetch_prague_tests = partial(fetch_state_test_files, network="Prague") - -FIXTURES_LOADER = Load("Prague", "prague") - -run_prague_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER -) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -ETHEREUM_SPEC_TESTS_PATH = TEST_FIXTURES["execution_spec_tests"][ - "fixture_path" -] - - -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +ETHEREUM_BLOCKCHAIN_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Prague" +PACKAGE = "prague" SLOW_TESTS = ( # GeneralStateTests @@ -82,40 +70,35 @@ "stStaticCall/", ) -fetch_state_tests = partial( - fetch_prague_tests, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) -# Run temporary test fixtures for Prague -test_dirs = ( - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7002_el_triggerable_withdrawals", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip6110_deposits", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7251_consolidations", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7685_general_purpose_el_requests", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip2537_bls_12_381_precompiles", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip2935_historical_block_hashes_from_state", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7702_set_code_tx", - "tests/fixtures/latest_fork_tests/blockchain_tests/prague/eip7623_increase_calldata_cost", -) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) -def fetch_temporary_tests(test_dirs: Tuple[str, ...]) -> Generator: - """ - Fetch the relevant tests for a particular EIP-Implementation - from among the temporary fixtures from ethereum-spec-tests. - """ - for test_dir in test_dirs: - yield from fetch_state_tests(test_dir) +# Run tests from ethereum/tests +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_temporary_tests(test_dirs), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_execution_specs_generated_tests(test_case: Dict) -> None: - run_prague_blockchain_st_tests(test_case) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/shanghai/test_evm_tools.py b/tests/shanghai/test_evm_tools.py index 071261e39f..4f7497ee7b 100644 --- a/tests/shanghai/test_evm_tools.py +++ b/tests/shanghai/test_evm_tools.py @@ -3,21 +3,18 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" -FORK_NAME = "Shanghai" - -run_evm_tools_test = partial( - load_evm_tools_test, - fork_name=FORK_NAME, +ETHEREUM_STATE_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Shanghai" SLOW_TESTS = ( "CALLBlake2f_MaxRounds", @@ -28,15 +25,36 @@ ) +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - SLOW_TESTS, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/shanghai/test_state_transition.py b/tests/shanghai/test_state_transition.py index 62a51f7228..700ad49756 100644 --- a/tests/shanghai/test_state_transition.py +++ b/tests/shanghai/test_state_transition.py @@ -3,7 +3,7 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -11,23 +11,14 @@ run_blockchain_st_test, ) -fetch_shanghai_tests = partial(fetch_state_test_files, network="Shanghai") - -FIXTURES_LOADER = Load("Shanghai", "shanghai") - -run_shanghai_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -ETHEREUM_SPEC_TESTS_PATH = TEST_FIXTURES["execution_spec_tests"][ - "fixture_path" -] +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Shanghai" +PACKAGE = "shanghai" -# Run state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Cancun/BlockchainTests/" - SLOW_TESTS = ( # GeneralStateTests "stTimeConsuming/CALLBlake2f_MaxRounds.json", @@ -74,32 +65,35 @@ "stStaticCall/", ) -fetch_state_tests = partial( - fetch_shanghai_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=IGNORE_TESTS, slow_list=SLOW_TESTS, big_memory_list=BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_general_state_tests(test_case: Dict) -> None: - run_shanghai_blockchain_st_tests(test_case) - - -# Run execution-spec-generated-tests -test_dir = f"{ETHEREUM_SPEC_TESTS_PATH}/fixtures/withdrawals" +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_shanghai_tests(test_dir), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_execution_specs_generated_tests(test_case: Dict) -> None: - run_shanghai_blockchain_st_tests(test_case) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/spurious_dragon/test_evm_tools.py b/tests/spurious_dragon/test_evm_tools.py index 66a76de0f4..5394bd5c1c 100644 --- a/tests/spurious_dragon/test_evm_tools.py +++ b/tests/spurious_dragon/test_evm_tools.py @@ -3,33 +3,51 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "EIP158" -run_evm_tools_test = partial( +SLOW_TESTS = () + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/spurious_dragon/test_state_transition.py b/tests/spurious_dragon/test_state_transition.py index d3af5e4788..dc2091b56a 100644 --- a/tests/spurious_dragon/test_state_transition.py +++ b/tests/spurious_dragon/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,19 +11,12 @@ run_blockchain_st_test, ) -fetch_spurious_dragon_tests = partial(fetch_state_test_files, network="EIP158") - -FIXTURES_LOADER = Load("EIP158", "spurious_dragon") - -run_spurious_dragon_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - - -# Run legacy general state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "EIP158" +PACKAGE = "spurious_dragon" LEGACY_SLOW_TESTS = ( # GeneralStateTests @@ -61,124 +49,35 @@ "GasLimitHigherThan2p63m1_EIP158", ) -fetch_legacy_state_tests = partial( - fetch_spurious_dragon_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=LEGACY_SLOW_TESTS, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_spurious_dragon_blockchain_st_tests(test_case) - - -# Run Non-Legacy State tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_spurious_dragon_tests(test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_state_tests(test_case: Dict) -> None: - run_spurious_dragon_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/tangerine_whistle/test_evm_tools.py b/tests/tangerine_whistle/test_evm_tools.py index 63b33108b7..94a6b8ab8a 100644 --- a/tests/tangerine_whistle/test_evm_tools.py +++ b/tests/tangerine_whistle/test_evm_tools.py @@ -3,33 +3,52 @@ import pytest -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_evm_tools_tests import ( fetch_evm_tools_tests, idfn, load_evm_tools_test, ) -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -TEST_DIR = ( +ETHEREUM_STATE_TESTS_DIR = ( f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/GeneralStateTests/" ) +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" FORK_NAME = "EIP150" -run_evm_tools_test = partial( + +SLOW_TESTS = () + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( load_evm_tools_test, fork_name=FORK_NAME, ) +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures @pytest.mark.evm_tools @pytest.mark.parametrize( "test_case", - fetch_evm_tools_tests( - TEST_DIR, - FORK_NAME, - ), + fetch_tests(EEST_STATE_TESTS_DIR), ids=idfn, ) -def test_evm_tools(test_case: Dict) -> None: - run_evm_tools_test(test_case) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/tangerine_whistle/test_state_transition.py b/tests/tangerine_whistle/test_state_transition.py index c56202145b..ba376d6aa7 100644 --- a/tests/tangerine_whistle/test_state_transition.py +++ b/tests/tangerine_whistle/test_state_transition.py @@ -2,13 +2,8 @@ from typing import Dict import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.numeric import U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock -from tests.helpers import TEST_FIXTURES +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH from tests.helpers.load_state_tests import ( Load, fetch_state_test_files, @@ -16,21 +11,12 @@ run_blockchain_st_test, ) -fetch_tangerine_whistle_tests = partial( - fetch_state_test_files, network="EIP150" +ETHEREUM_BLOCKCHAIN_TESTS_DIR = ( + f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" ) - -FIXTURES_LOADER = Load("EIP150", "tangerine_whistle") - -run_tangerine_whistle_blockchain_st_tests = partial( - run_blockchain_st_test, load=FIXTURES_LOADER -) - -ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - - -# Run legacy state tests -test_dir = f"{ETHEREUM_TESTS_PATH}/LegacyTests/Constantinople/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "EIP150" +PACKAGE = "tangerine_whistle" LEGACY_SLOW_TESTS = ( "stRandom/randomStatetest177.json", @@ -63,124 +49,35 @@ "GasLimitHigherThan2p63m1_EIP150", ) -fetch_legacy_state_tests = partial( - fetch_tangerine_whistle_tests, - test_dir, +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, ignore_list=LEGACY_IGNORE_LIST, slow_list=LEGACY_SLOW_TESTS, big_memory_list=LEGACY_BIG_MEMORY_TESTS, ) +FIXTURES_LOADER = Load(NETWORK, PACKAGE) +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests @pytest.mark.parametrize( "test_case", - fetch_legacy_state_tests(), + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_legacy_state_tests(test_case: Dict) -> None: - run_tangerine_whistle_blockchain_st_tests(test_case) - - -# Run Non-Legacy State Tests -test_dir = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/GeneralStateTests/" - -non_legacy_only_in = ( - "stCreateTest/CREATE_HighNonce.json", - "stCreateTest/CREATE_HighNonceMinus1.json", -) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) +# Run EEST test fixtures @pytest.mark.parametrize( "test_case", - fetch_tangerine_whistle_tests(test_dir, only_in=non_legacy_only_in), + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), ids=idfn, ) -def test_non_legacy_state_tests(test_case: Dict) -> None: - run_tangerine_whistle_blockchain_st_tests(test_case) - - -def test_transaction_with_insufficient_balance_for_value() -> None: - genesis_header = FIXTURES_LOADER.fork.Header( - parent_hash=Hash32([0] * 32), - ommers_hash=Hash32.fromhex( - "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - ), - coinbase=FIXTURES_LOADER.fork.hex_to_address( - "8888f1f195afa192cfee860698584c030f4c9db1" - ), - state_root=FIXTURES_LOADER.fork.hex_to_root( - "d84598d90e2a72125c111171717f5508fd40ed0d0cd067ceb4e734d4da3a810a" - ), - transactions_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - receipt_root=FIXTURES_LOADER.fork.hex_to_root( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ), - bloom=FIXTURES_LOADER.fork.Bloom([0] * 256), - difficulty=Uint(0x020000), - number=Uint(0x00), - gas_limit=Uint(0x2FEFD8), - gas_used=Uint(0x00), - timestamp=U256(0x54C98C81), - extra_data=Bytes([0x42]), - mix_digest=Bytes32([0] * 32), - nonce=Bytes8([0] * 8), - ) - - genesis_header_hash = bytes.fromhex( - "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" - ) - - assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash - - genesis_block = FIXTURES_LOADER.fork.Block( - genesis_header, - (), - (), - ) - - state = FIXTURES_LOADER.fork.State() - - address = FIXTURES_LOADER.fork.hex_to_address( - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ) - - account = FIXTURES_LOADER.fork.Account( - nonce=Uint(0), - balance=U256(0x056BC75E2D63100000), - code=Bytes(), - ) - - FIXTURES_LOADER.fork.set_account(state, address, account) - - tx = FIXTURES_LOADER.fork.Transaction( - nonce=U256(0x00), - gas_price=Uint(1000), - gas=Uint(150000), - to=FIXTURES_LOADER.fork.hex_to_address( - "c94f5374fce5edbc8e2a8697c15331677e6ebf0b" - ), - value=U256(1000000000000000000000), - data=Bytes(), - v=U256(0), - r=U256(0), - s=U256(0), - ) - - env = FIXTURES_LOADER.fork.Environment( - caller=address, - origin=address, - block_hashes=[genesis_header_hash], - coinbase=genesis_block.header.coinbase, - number=genesis_block.header.number + Uint(1), - gas_limit=genesis_block.header.gas_limit, - gas_price=tx.gas_price, - time=genesis_block.header.timestamp, - difficulty=genesis_block.header.difficulty, - state=state, - traces=[], - ) - - with pytest.raises(InvalidBlock): - FIXTURES_LOADER.fork.process_transaction(env, tx) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/whitelist.txt b/whitelist.txt index 8281e2ff3b..74cbb64d59 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -464,3 +464,6 @@ impl x2 eoa + +blockchain +listdir \ No newline at end of file From d55fca74ab47a6acb28000144a7a46950674a016 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Mon, 10 Mar 2025 11:23:57 +0100 Subject: [PATCH 28/70] add state test flag --- src/ethereum_spec_tools/evm_tools/statetest/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py index 1e1ac04bcb..6ecca69ad5 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py @@ -135,6 +135,7 @@ def run_test_case( "stdin", "--state.fork", f"{test_case.fork_name}", + "--state-test", ] if t8n_extra is not None: From efe7bfdf0f7de3ea41d922217a78e32bd3eaf2aa Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Mon, 10 Mar 2025 11:52:01 +0100 Subject: [PATCH 29/70] output traces to correct location --- src/ethereum_spec_tools/evm_tools/t8n/__init__.py | 1 + src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py | 10 ++++++---- tests/conftest.py | 4 ---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 4d9de304fc..23415756e1 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -109,6 +109,7 @@ def __init__( trace_memory=trace_memory, trace_stack=trace_stack, trace_return_data=trace_return_data, + output_basedir=self.options.output_basedir, ) self.logger = get_stream_logger("T8N") diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py index ab0603c6b4..d396b3df19 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py @@ -34,7 +34,6 @@ ) EXCLUDE_FROM_OUTPUT = ["gasCostTraced", "errorTraced", "precompile"] -OUTPUT_DIR = "." @dataclass @@ -143,6 +142,7 @@ def evm_trace( trace_memory: bool = False, trace_stack: bool = True, trace_return_data: bool = False, + output_basedir: str | TextIO = ".", ) -> None: """ Create a trace of the event. @@ -191,6 +191,7 @@ def evm_trace( traces, evm.message.tx_env.index_in_block, evm.message.tx_env.tx_hash, + output_basedir, ) elif isinstance(event, PrecompileStart): new_trace = Trace( @@ -351,6 +352,7 @@ def output_traces( traces: List[Union[Trace, FinalTrace]], index_in_block: int, tx_hash: bytes, + output_basedir: str | TextIO, ) -> None: """ Output the traces to a json file. @@ -358,15 +360,15 @@ def output_traces( with ExitStack() as stack: json_file: TextIO - if isinstance(OUTPUT_DIR, str): + if isinstance(output_basedir, str): tx_hash_str = "0x" + tx_hash.hex() output_path = os.path.join( - OUTPUT_DIR, f"trace-{index_in_block}-{tx_hash_str}.jsonl" + output_basedir, f"trace-{index_in_block}-{tx_hash_str}.jsonl" ) json_file = open(output_path, "w") stack.push(json_file) else: - json_file = OUTPUT_DIR + json_file = output_basedir for trace in traces: if getattr(trace, "precompile", False): diff --git a/tests/conftest.py b/tests/conftest.py index d61805f4b4..c1f7d03b2e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,17 +47,13 @@ def pytest_configure(config: Config) -> None: ethereum_optimized.monkey_patch(None) if config.getoption("evm_trace"): - path = config.getoption("evm_trace") import ethereum.trace - import ethereum_spec_tools.evm_tools.t8n.evm_trace as evm_trace_module from ethereum_spec_tools.evm_tools.t8n.evm_trace import ( evm_trace as new_trace_function, ) # Replace the function in the module ethereum.trace.evm_trace = new_trace_function - # Set the output directory for traces - evm_trace_module.OUTPUT_DIR = path def download_fixtures(url: str, location: str) -> None: From 8f8fb217681925d96470a17af74f0892a0783445 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Tue, 11 Mar 2025 08:30:32 +0100 Subject: [PATCH 30/70] update ethereum tests to latest --- tests/helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index b25e2049ff..42fae17f80 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -8,7 +8,7 @@ }, "ethereum_tests": { "url": "https://github.com/ethereum/tests.git", - "commit_hash": "a0e8482", + "commit_hash": "afed83b", "fixture_path": "tests/fixtures/ethereum_tests", }, "latest_fork_tests": { From fd4f4bc562f1c561e1ca9d89f78cd80f47c40476 Mon Sep 17 00:00:00 2001 From: Shashwat_Nautiyal <148687529+Shashwat-Nautiyal@users.noreply.github.com> Date: Wed, 12 Mar 2025 11:53:46 +0530 Subject: [PATCH 31/70] validate_transaction now raises InvalidTransaction exception (#1138) * validate_transaction now raises InvalidTransaction * Update src/ethereum/cancun/transactions.py Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> * Update src/ethereum/cancun/transactions.py Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> * Update src/ethereum/cancun/transactions.py Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> * exception specified within InvalidTransaction * isort formatting issue resolved * fix issues from static checks --------- Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Co-authored-by: Guruprasad Kamath --- src/ethereum/arrow_glacier/transactions.py | 8 ++++---- src/ethereum/berlin/transactions.py | 8 ++++---- src/ethereum/byzantium/transactions.py | 8 ++++---- src/ethereum/cancun/transactions.py | 10 +++++----- src/ethereum/constantinople/transactions.py | 8 ++++---- src/ethereum/dao_fork/transactions.py | 8 ++++---- src/ethereum/frontier/transactions.py | 8 ++++---- src/ethereum/gray_glacier/transactions.py | 8 ++++---- src/ethereum/homestead/transactions.py | 8 ++++---- src/ethereum/istanbul/transactions.py | 8 ++++---- src/ethereum/london/transactions.py | 8 ++++---- src/ethereum/muir_glacier/transactions.py | 8 ++++---- src/ethereum/paris/transactions.py | 8 ++++---- src/ethereum/prague/transactions.py | 8 ++++---- src/ethereum/shanghai/transactions.py | 10 +++++----- src/ethereum/spurious_dragon/transactions.py | 8 ++++---- src/ethereum/tangerine_whistle/transactions.py | 8 ++++---- tests/berlin/test_transaction.py | 4 ++-- tests/byzantium/test_transaction.py | 4 ++-- tests/constantinople/test_transaction.py | 4 ++-- tests/frontier/test_transaction.py | 4 ++-- tests/homestead/test_transaction.py | 4 ++-- tests/istanbul/test_transaction.py | 4 ++-- tests/london/test_transaction.py | 4 ++-- tests/spurious_dragon/test_transaction.py | 4 ++-- tests/tangerine_whistle/test_transaction.py | 4 ++-- 26 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index d4a017576d..57dbe9ec50 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -146,14 +146,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 355219eaa1..432d942906 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -118,14 +118,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index 21ffbc1b6f..65e622b5e1 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index d4d8823f72..8865fe846f 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash @@ -176,18 +176,18 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ from .vm.interpreter import MAX_CODE_SIZE intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - raise InvalidBlock + raise InvalidTransaction("Code size too large") return intrinsic_gas diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index 21ffbc1b6f..65e622b5e1 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index 6db00e736d..6761b34ca9 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index e9801dfafe..99e6a6bb05 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -67,14 +67,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("nonce too high") return intrinsic_gas diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index d4a017576d..57dbe9ec50 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -146,14 +146,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index 6db00e736d..6761b34ca9 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index 28115976e8..4f2f9efe6d 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index d4a017576d..57dbe9ec50 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -146,14 +146,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index 28115976e8..4f2f9efe6d 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index d4a017576d..57dbe9ec50 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -146,14 +146,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index 4bfc077b02..3e5adcfb46 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address, Authorization, VersionedHash @@ -213,11 +213,11 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - raise InvalidBlock + raise InvalidTransaction("Code size too large") return intrinsic_gas, calldata_floor_gas_cost diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index 06cfa077ac..f7a62acf9b 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .exceptions import TransactionTypeError from .fork_types import Address @@ -146,18 +146,18 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ from .vm.interpreter import MAX_CODE_SIZE intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - raise InvalidBlock + raise InvalidTransaction("Code size too large") return intrinsic_gas diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index 21ffbc1b6f..65e622b5e1 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index 6db00e736d..6761b34ca9 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -13,7 +13,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction from .fork_types import Address @@ -68,14 +68,14 @@ def validate_transaction(tx: Transaction) -> Uint: Raises ------ - InvalidBlock : + InvalidTransaction : If the transaction is not valid. """ intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: - raise InvalidBlock + raise InvalidTransaction("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidBlock + raise InvalidTransaction("Nonce too high") return intrinsic_gas diff --git a/tests/berlin/test_transaction.py b/tests/berlin/test_transaction.py index 280530d07a..ce5fc68e21 100644 --- a/tests/berlin/test_transaction.py +++ b/tests/berlin/test_transaction.py @@ -7,7 +7,7 @@ LegacyTransaction, validate_transaction, ) -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -33,7 +33,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(LegacyTransaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/byzantium/test_transaction.py b/tests/byzantium/test_transaction.py index a9003bac9e..acb3e9070c 100644 --- a/tests/byzantium/test_transaction.py +++ b/tests/byzantium/test_transaction.py @@ -4,7 +4,7 @@ from ethereum_rlp import rlp from ethereum.byzantium.transactions import Transaction, validate_transaction -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -32,7 +32,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/constantinople/test_transaction.py b/tests/constantinople/test_transaction.py index 367bf2e4b3..b11626d45d 100644 --- a/tests/constantinople/test_transaction.py +++ b/tests/constantinople/test_transaction.py @@ -7,7 +7,7 @@ Transaction, validate_transaction, ) -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -35,7 +35,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/frontier/test_transaction.py b/tests/frontier/test_transaction.py index dff4ab44bb..a2e72140d4 100644 --- a/tests/frontier/test_transaction.py +++ b/tests/frontier/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.frontier.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -30,7 +30,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/homestead/test_transaction.py b/tests/homestead/test_transaction.py index 3f30927fed..7562afa3d5 100644 --- a/tests/homestead/test_transaction.py +++ b/tests/homestead/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.homestead.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -32,7 +32,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/istanbul/test_transaction.py b/tests/istanbul/test_transaction.py index ec46c371b8..37a096445d 100644 --- a/tests/istanbul/test_transaction.py +++ b/tests/istanbul/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.istanbul.transactions import Transaction, validate_transaction from ethereum.utils.hexadecimal import hex_to_uint from tests.helpers import TEST_FIXTURES @@ -30,7 +30,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/london/test_transaction.py b/tests/london/test_transaction.py index 760ad3351b..5925474afe 100644 --- a/tests/london/test_transaction.py +++ b/tests/london/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.london.transactions import ( LegacyTransaction, validate_transaction, @@ -33,7 +33,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(LegacyTransaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/spurious_dragon/test_transaction.py b/tests/spurious_dragon/test_transaction.py index ad68dafd68..5e25d340ac 100644 --- a/tests/spurious_dragon/test_transaction.py +++ b/tests/spurious_dragon/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.spurious_dragon.transactions import ( Transaction, validate_transaction, @@ -35,7 +35,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) diff --git a/tests/tangerine_whistle/test_transaction.py b/tests/tangerine_whistle/test_transaction.py index 3d70cff571..455630e8a3 100644 --- a/tests/tangerine_whistle/test_transaction.py +++ b/tests/tangerine_whistle/test_transaction.py @@ -3,7 +3,7 @@ import pytest from ethereum_rlp import rlp -from ethereum.exceptions import InvalidBlock +from ethereum.exceptions import InvalidTransaction from ethereum.tangerine_whistle.transactions import ( Transaction, validate_transaction, @@ -35,7 +35,7 @@ def test_high_nonce(test_file_high_nonce: str) -> None: tx = rlp.decode_to(Transaction, test["tx_rlp"]) - with pytest.raises(InvalidBlock): + with pytest.raises(InvalidTransaction): validate_transaction(tx) From 8f89be57c9f1c764d1fa5b95eb32db0eac7fd3d6 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Thu, 13 Mar 2025 05:51:36 +0100 Subject: [PATCH 32/70] default to empty accesslist when unavailable (#1151) --- .../evm_tools/loaders/transaction_loader.py | 4 ++-- src/ethereum_spec_tools/evm_tools/statetest/__init__.py | 2 +- tests/helpers/load_evm_tools_tests.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py index 70c1e8d82a..b788601400 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -76,7 +76,7 @@ def json_to_data(self) -> Bytes: def json_to_access_list(self) -> Any: """Get the access list of the transaction.""" access_list = [] - for sublist in self.raw["accessList"]: + for sublist in self.raw.get("accessLists", []): access_list.append( ( self.fork.hex_to_address(sublist.get("address")), @@ -192,7 +192,7 @@ def read(self) -> Any: elif "maxFeePerGas" in self.raw: tx_cls = self.fork.FeeMarketTransaction tx_byte_prefix = b"\x02" - elif "accessList" in self.raw: + elif "accessLists" in self.raw: tx_cls = self.fork.AccessListTransaction tx_byte_prefix = b"\x01" else: diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py index 6ecca69ad5..7b42033892 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py @@ -107,7 +107,7 @@ def run_test_case( tx[k] = value[v] elif k == "accessLists": if value[d] is not None: - tx["accessList"] = value[d] + tx["accessLists"] = value[d] else: tx[k] = value diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index 38af0eafff..66d183523b 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -95,7 +95,7 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: tx[k] = value[v] elif k == "accessLists": if value[d] is not None: - tx["accessList"] = value[d] + tx["accessLists"] = value[d] else: tx[k] = value From b22a46a98f0b95830398cbc97044f45a8ab6b11d Mon Sep 17 00:00:00 2001 From: Limer_Beast <153820464+Antrikshgwal@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:38:53 +0530 Subject: [PATCH 33/70] Simplified validate_header func in post-merge forks (#1163) * Simplified validate_header func in post-merge forks * Simplified parent hash calculation in post-merge forks * validate_header modification in paris and prague forks * fix formatting --------- Co-authored-by: Guruprasad Kamath --- src/ethereum/cancun/fork.py | 13 +------------ src/ethereum/paris/fork.py | 13 +------------ src/ethereum/prague/fork.py | 13 +------------ src/ethereum/shanghai/fork.py | 13 +------------ 4 files changed, 4 insertions(+), 48 deletions(-) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 7932db33d3..89426989c5 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -310,19 +310,8 @@ def validate_header(chain: BlockChain, header: Header) -> None: """ if header.number < Uint(1): raise InvalidBlock - parent_header_number = header.number - Uint(1) - first_block_number = chain.blocks[0].header.number - last_block_number = chain.blocks[-1].header.number - if ( - parent_header_number < first_block_number - or parent_header_number > last_block_number - ): - raise InvalidBlock - - parent_header = chain.blocks[ - parent_header_number - first_block_number - ].header + parent_header = chain.blocks[-1].header excess_blob_gas = calculate_excess_blob_gas(parent_header) if header.excess_blob_gas != excess_blob_gas: diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index ac9a916675..714a68864e 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -284,19 +284,8 @@ def validate_header(chain: BlockChain, header: Header) -> None: """ if header.number < Uint(1): raise InvalidBlock - parent_header_number = header.number - Uint(1) - first_block_number = chain.blocks[0].header.number - last_block_number = chain.blocks[-1].header.number - - if ( - parent_header_number < first_block_number - or parent_header_number > last_block_number - ): - raise InvalidBlock - parent_header = chain.blocks[ - parent_header_number - first_block_number - ].header + parent_header = chain.blocks[-1].header if header.gas_used > header.gas_limit: raise InvalidBlock diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 50b5033e53..9b9beaaa4b 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -333,19 +333,8 @@ def validate_header(chain: BlockChain, header: Header) -> None: """ if header.number < Uint(1): raise InvalidBlock - parent_header_number = header.number - Uint(1) - first_block_number = chain.blocks[0].header.number - last_block_number = chain.blocks[-1].header.number - if ( - parent_header_number < first_block_number - or parent_header_number > last_block_number - ): - raise InvalidBlock - - parent_header = chain.blocks[ - parent_header_number - first_block_number - ].header + parent_header = chain.blocks[-1].header excess_blob_gas = calculate_excess_blob_gas(parent_header) if header.excess_blob_gas != excess_blob_gas: diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 46e39874c1..0dbf404b78 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -289,19 +289,8 @@ def validate_header(chain: BlockChain, header: Header) -> None: """ if header.number < Uint(1): raise InvalidBlock - parent_header_number = header.number - Uint(1) - first_block_number = chain.blocks[0].header.number - last_block_number = chain.blocks[-1].header.number - - if ( - parent_header_number < first_block_number - or parent_header_number > last_block_number - ): - raise InvalidBlock - parent_header = chain.blocks[ - parent_header_number - first_block_number - ].header + parent_header = chain.blocks[-1].header if header.gas_used > header.gas_limit: raise InvalidBlock From 586b03bfa04c8493fba4d2a2857416553082851e Mon Sep 17 00:00:00 2001 From: Shashwat_Nautiyal <148687529+Shashwat-Nautiyal@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:14:57 +0530 Subject: [PATCH 34/70] Update the type for `blob_gas_used` (#1161) * type for blob_gas_used updated * type inconsitencies in op resolved --- src/ethereum/cancun/fork.py | 6 +++--- src/ethereum/cancun/vm/__init__.py | 4 ++-- src/ethereum/cancun/vm/gas.py | 10 +++++----- src/ethereum/prague/fork.py | 6 +++--- src/ethereum/prague/vm/__init__.py | 4 ++-- src/ethereum/prague/vm/gas.py | 10 +++++----- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 89426989c5..b4ae716a80 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -76,7 +76,7 @@ "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) SYSTEM_TRANSACTION_GAS = Uint(30000000) -MAX_BLOB_GAS_PER_BLOCK = Uint(786432) +MAX_BLOB_GAS_PER_BLOCK = U64(786432) VERSIONED_HASH_VERSION_KZG = b"\x01" @@ -350,7 +350,7 @@ def check_transaction( block_env: vm.BlockEnvironment, block_output: vm.BlockOutput, tx: Transaction, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -423,7 +423,7 @@ def check_transaction( if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: raise InvalidBlock - max_gas_fee += calculate_total_blob_gas(tx) * Uint( + max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint( tx.max_fee_per_blob_gas ) blob_versioned_hashes = tx.blob_versioned_hashes diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index be19e8081b..811bbab24e 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -69,7 +69,7 @@ class BlockOutput: block. withdrawals_trie : `ethereum.fork_types.Root` Trie root of all the withdrawals in the block. - blob_gas_used : `ethereum.base_types.Uint` + blob_gas_used : `ethereum.base_types.U64` Total blob gas used in the block. """ @@ -84,7 +84,7 @@ class BlockOutput: withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) - blob_gas_used: Uint = Uint(0) + blob_gas_used: U64 = U64(0) @dataclass diff --git a/src/ethereum/cancun/vm/gas.py b/src/ethereum/cancun/vm/gas.py index 794396cbf2..50d7dd1b1e 100644 --- a/src/ethereum/cancun/vm/gas.py +++ b/src/ethereum/cancun/vm/gas.py @@ -69,7 +69,7 @@ GAS_POINT_EVALUATION = Uint(50000) TARGET_BLOB_GAS_PER_BLOCK = U64(393216) -GAS_PER_BLOB = Uint(2**17) +GAS_PER_BLOB = U64(2**17) MIN_BLOB_GASPRICE = Uint(1) BLOB_BASE_FEE_UPDATE_FRACTION = Uint(3338477) @@ -300,7 +300,7 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK -def calculate_total_blob_gas(tx: Transaction) -> Uint: +def calculate_total_blob_gas(tx: Transaction) -> U64: """ Calculate the total blob gas for a transaction. @@ -315,9 +315,9 @@ def calculate_total_blob_gas(tx: Transaction) -> Uint: The total blob gas for the transaction. """ if isinstance(tx, BlobTransaction): - return GAS_PER_BLOB * Uint(len(tx.blob_versioned_hashes)) + return GAS_PER_BLOB * U64(len(tx.blob_versioned_hashes)) else: - return Uint(0) + return U64(0) def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: @@ -357,6 +357,6 @@ def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: data_fee: `Uint` The blob data fee. """ - return calculate_total_blob_gas(tx) * calculate_blob_gas_price( + return Uint(calculate_total_blob_gas(tx)) * calculate_blob_gas_price( excess_blob_gas ) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 9b9beaaa4b..1058b7c288 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -85,7 +85,7 @@ "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) SYSTEM_TRANSACTION_GAS = Uint(30000000) -MAX_BLOB_GAS_PER_BLOCK = Uint(1179648) +MAX_BLOB_GAS_PER_BLOCK = U64(1179648) VERSIONED_HASH_VERSION_KZG = b"\x01" WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( @@ -373,7 +373,7 @@ def check_transaction( block_env: vm.BlockEnvironment, block_output: vm.BlockOutput, tx: Transaction, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -446,7 +446,7 @@ def check_transaction( if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: raise InvalidBlock - max_gas_fee += calculate_total_blob_gas(tx) * Uint( + max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint( tx.max_fee_per_blob_gas ) blob_versioned_hashes = tx.blob_versioned_hashes diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 55f1b92499..9e91cebc2c 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -69,7 +69,7 @@ class BlockOutput: block. withdrawals_trie : `ethereum.fork_types.Root` Trie root of all the withdrawals in the block. - blob_gas_used : `ethereum.base_types.Uint` + blob_gas_used : `ethereum.base_types.U64` Total blob gas used in the block. requests : `Bytes` Hash of all the requests in the block. @@ -86,7 +86,7 @@ class BlockOutput: withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) - blob_gas_used: Uint = Uint(0) + blob_gas_used: U64 = U64(0) deposit_requests: Bytes = Bytes(b"") requests: List[Bytes] = field(default_factory=list) diff --git a/src/ethereum/prague/vm/gas.py b/src/ethereum/prague/vm/gas.py index 624a8f86e2..fd31799b55 100644 --- a/src/ethereum/prague/vm/gas.py +++ b/src/ethereum/prague/vm/gas.py @@ -69,7 +69,7 @@ GAS_POINT_EVALUATION = Uint(50000) TARGET_BLOB_GAS_PER_BLOCK = U64(786432) -GAS_PER_BLOB = Uint(2**17) +GAS_PER_BLOB = U64(2**17) MIN_BLOB_GASPRICE = Uint(1) BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716) @@ -307,7 +307,7 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK -def calculate_total_blob_gas(tx: Transaction) -> Uint: +def calculate_total_blob_gas(tx: Transaction) -> U64: """ Calculate the total blob gas for a transaction. @@ -322,9 +322,9 @@ def calculate_total_blob_gas(tx: Transaction) -> Uint: The total blob gas for the transaction. """ if isinstance(tx, BlobTransaction): - return GAS_PER_BLOB * Uint(len(tx.blob_versioned_hashes)) + return GAS_PER_BLOB * U64(len(tx.blob_versioned_hashes)) else: - return Uint(0) + return U64(0) def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: @@ -364,6 +364,6 @@ def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: data_fee: `Uint` The blob data fee. """ - return calculate_total_blob_gas(tx) * calculate_blob_gas_price( + return Uint(calculate_total_blob_gas(tx)) * calculate_blob_gas_price( excess_blob_gas ) From ce310e49ae551c666879807c0711d99106537bd2 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:52:24 +0200 Subject: [PATCH 35/70] EELS Update: Update EIP-6110 (#1143) * check log topic for deposit requests * check deposit log validity * use Uint for parsing deposit logs --- src/ethereum/prague/requests.py | 129 ++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index a72f8f35ae..129f1e6c2c 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -12,6 +12,10 @@ from typing import List, Union from ethereum_types.bytes import Bytes +from ethereum_types.numeric import Uint, ulen + +from ethereum.exceptions import InvalidBlock +from ethereum.utils.hexadecimal import hex_to_bytes32 from .blocks import Receipt, decode_receipt from .utils.hexadecimal import hex_to_address @@ -19,22 +23,127 @@ DEPOSIT_CONTRACT_ADDRESS = hex_to_address( "0x00000000219ab540356cbb839cbe05303d7705fa" ) +DEPOSIT_EVENT_SIGNATURE_HASH = hex_to_bytes32( + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5" +) DEPOSIT_REQUEST_TYPE = b"\x00" WITHDRAWAL_REQUEST_TYPE = b"\x01" CONSOLIDATION_REQUEST_TYPE = b"\x02" +DEPOSIT_EVENT_LENGTH = Uint(576) + +PUBKEY_OFFSET = Uint(160) +WITHDRAWAL_CREDENTIALS_OFFSET = Uint(256) +AMOUNT_OFFSET = Uint(320) +SIGNATURE_OFFSET = Uint(384) +INDEX_OFFSET = Uint(512) + +PUBKEY_SIZE = Uint(48) +WITHDRAWAL_CREDENTIALS_SIZE = Uint(32) +AMOUNT_SIZE = Uint(8) +SIGNATURE_SIZE = Uint(96) +INDEX_SIZE = Uint(8) + + def extract_deposit_data(data: Bytes) -> Bytes: """ Extracts Deposit Request from the DepositContract.DepositEvent data. + + Raises + ------ + InvalidBlock : + If the deposit contract did not produce a valid log. """ - return ( - data[192:240] # public_key - + data[288:320] # withdrawal_credentials - + data[352:360] # amount - + data[416:512] # signature - + data[544:552] # index + if ulen(data) != DEPOSIT_EVENT_LENGTH: + raise InvalidBlock("Invalid deposit event data length") + + # Check that all the offsets are in order + pubkey_offset = Uint.from_be_bytes(data[0:32]) + if pubkey_offset != PUBKEY_OFFSET: + raise InvalidBlock("Invalid pubkey offset in deposit log") + + withdrawal_credentials_offset = Uint.from_be_bytes(data[32:64]) + if withdrawal_credentials_offset != WITHDRAWAL_CREDENTIALS_OFFSET: + raise InvalidBlock( + "Invalid withdrawal credentials offset in deposit log" + ) + + amount_offset = Uint.from_be_bytes(data[64:96]) + if amount_offset != AMOUNT_OFFSET: + raise InvalidBlock("Invalid amount offset in deposit log") + + signature_offset = Uint.from_be_bytes(data[96:128]) + if signature_offset != SIGNATURE_OFFSET: + raise InvalidBlock("Invalid signature offset in deposit log") + + index_offset = Uint.from_be_bytes(data[128:160]) + if index_offset != INDEX_OFFSET: + raise InvalidBlock("Invalid index offset in deposit log") + + # Check that all the sizes are in order + pubkey_size = Uint.from_be_bytes( + data[pubkey_offset : pubkey_offset + Uint(32)] ) + if pubkey_size != PUBKEY_SIZE: + raise InvalidBlock("Invalid pubkey size in deposit log") + + pubkey = data[ + pubkey_offset + Uint(32) : pubkey_offset + Uint(32) + PUBKEY_SIZE + ] + + withdrawal_credentials_size = Uint.from_be_bytes( + data[ + withdrawal_credentials_offset : withdrawal_credentials_offset + + Uint(32) + ], + ) + if withdrawal_credentials_size != WITHDRAWAL_CREDENTIALS_SIZE: + raise InvalidBlock( + "Invalid withdrawal credentials size in deposit log" + ) + + withdrawal_credentials = data[ + withdrawal_credentials_offset + + Uint(32) : withdrawal_credentials_offset + + Uint(32) + + WITHDRAWAL_CREDENTIALS_SIZE + ] + + amount_size = Uint.from_be_bytes( + data[amount_offset : amount_offset + Uint(32)] + ) + if amount_size != AMOUNT_SIZE: + raise InvalidBlock("Invalid amount size in deposit log") + + amount = data[ + amount_offset + Uint(32) : amount_offset + Uint(32) + AMOUNT_SIZE + ] + + signature_size = Uint.from_be_bytes( + data[signature_offset : signature_offset + Uint(32)] + ) + if signature_size != SIGNATURE_SIZE: + raise InvalidBlock("Invalid signature size in deposit log") + + signature = data[ + signature_offset + + Uint(32) : signature_offset + + Uint(32) + + SIGNATURE_SIZE + ] + + index_size = Uint.from_be_bytes( + data[index_offset : index_offset + Uint(32)] + ) + if index_size != INDEX_SIZE: + raise InvalidBlock("Invalid index size in deposit log") + + index = data[ + index_offset + Uint(32) : index_offset + Uint(32) + INDEX_SIZE + ] + + return pubkey + withdrawal_credentials + amount + signature + index def parse_deposit_requests_from_receipt( @@ -47,8 +156,12 @@ def parse_deposit_requests_from_receipt( decoded_receipt = decode_receipt(receipt) for log in decoded_receipt.logs: if log.address == DEPOSIT_CONTRACT_ADDRESS: - request = extract_deposit_data(log.data) - deposit_requests += request + if ( + len(log.topics) > 0 + and log.topics[0] == DEPOSIT_EVENT_SIGNATURE_HASH + ): + request = extract_deposit_data(log.data) + deposit_requests += request return deposit_requests From 408a907390edc9826dc5f3002d085a6c41d32717 Mon Sep 17 00:00:00 2001 From: Shashwat_Nautiyal <148687529+Shashwat-Nautiyal@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:49:22 +0530 Subject: [PATCH 36/70] added address after checks (#1129) * address gets added only after checks * linting corrected * refactored as per geth impl --- src/ethereum/arrow_glacier/vm/instructions/system.py | 5 +++-- src/ethereum/berlin/vm/instructions/system.py | 5 +++-- src/ethereum/cancun/vm/instructions/system.py | 5 +++-- src/ethereum/gray_glacier/vm/instructions/system.py | 5 +++-- src/ethereum/london/vm/instructions/system.py | 5 +++-- src/ethereum/paris/vm/instructions/system.py | 5 +++-- src/ethereum/prague/vm/instructions/system.py | 5 +++-- src/ethereum/shanghai/vm/instructions/system.py | 5 +++-- 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/ethereum/arrow_glacier/vm/instructions/system.py b/src/ethereum/arrow_glacier/vm/instructions/system.py index 7a2f1efeb3..c08b42ccb8 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/arrow_glacier/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -75,8 +76,6 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -95,6 +94,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/berlin/vm/instructions/system.py b/src/ethereum/berlin/vm/instructions/system.py index a561423a0f..57ec1826dd 100644 --- a/src/ethereum/berlin/vm/instructions/system.py +++ b/src/ethereum/berlin/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -76,8 +77,6 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -96,6 +95,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index ff473fc285..9e503d4a72 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -84,8 +85,6 @@ def generic_create( if len(call_data) > 2 * MAX_CODE_SIZE: raise OutOfGasError - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -104,6 +103,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/gray_glacier/vm/instructions/system.py b/src/ethereum/gray_glacier/vm/instructions/system.py index 7a2f1efeb3..c08b42ccb8 100644 --- a/src/ethereum/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/gray_glacier/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -75,8 +76,6 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -95,6 +94,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/london/vm/instructions/system.py b/src/ethereum/london/vm/instructions/system.py index 7a2f1efeb3..c08b42ccb8 100644 --- a/src/ethereum/london/vm/instructions/system.py +++ b/src/ethereum/london/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -75,8 +76,6 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -95,6 +94,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index 7a2f1efeb3..c08b42ccb8 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -75,8 +76,6 @@ def generic_create( evm.memory, memory_start_position, memory_size ) - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -95,6 +94,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 88ba466e8e..7e8004b1ea 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -85,8 +86,6 @@ def generic_create( if len(call_data) > 2 * MAX_CODE_SIZE: raise OutOfGasError - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -105,6 +104,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index f214469225..86001b1646 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -11,6 +11,7 @@ Implementations of the EVM system related instructions. """ + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -83,8 +84,6 @@ def generic_create( if len(call_data) > 2 * MAX_CODE_SIZE: raise OutOfGasError - evm.accessed_addresses.add(contract_address) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas if evm.message.is_static: @@ -103,6 +102,8 @@ def generic_create( push(evm.stack, U256(0)) return + 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): From 114c063844a6275f1ef542eb248c8a59aabb0c91 Mon Sep 17 00:00:00 2001 From: carsons-eels Date: Tue, 8 Apr 2025 07:20:37 -0400 Subject: [PATCH 37/70] Update codecov - Exclude *glacier & DAO forks from coverage - Add codecov badge to README --- README.md | 1 + pyproject.toml | 6 ++++++ tox.ini | 7 ++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bbedf8eb5..e7db3c74bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Ethereum Execution Client Specifications [![GitPOAP Badge](https://public-api.gitpoap.io/v1/repo/ethereum/execution-specs/badge)](https://www.gitpoap.io/gh/ethereum/execution-specs) +[![codecov](https://codecov.io/gh/ethereum/execution-specs/graph/badge.svg?token=0LQZO56RTM)](https://codecov.io/gh/ethereum/execution-specs) ## Description diff --git a/pyproject.toml b/pyproject.toml index 218fdafc40..7f7fcd68a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,12 @@ markers = [ "evm_tools: marks tests as evm_tools (deselect with '-m \"not evm_tools\"')", ] +[tool.coverage.run] +omit = [ + "*/ethereum/*_glacier/*", + "*/ethereum/dao_fork/*", +] + [tool.docc] context = [ "docc.references.context", diff --git a/tox.ini b/tox.ini index 647714587b..3ac82c90c5 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,12 @@ commands = pytest \ -m "not slow" \ -n auto --maxprocesses 5 \ - --cov=ethereum --cov-report=term --cov-report "xml:{toxworkdir}/coverage.xml" \ + --cov-config=pyproject.toml \ + --cov=ethereum \ + --cov-report=term \ + --cov-report "xml:{toxworkdir}/coverage.xml" \ + --no-cov-on-fail \ + --cov-branch \ --ignore-glob='tests/fixtures/*' \ --basetemp="{temp_dir}/pytest" From 5108203c56e05cd3bd7306b58baf3a0c37a2c4ee Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Mon, 7 Apr 2025 13:04:28 +0200 Subject: [PATCH 38/70] delete only select files --- .../evm_tools/t8n/__init__.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 23415756e1..c9cb8e1477 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -3,6 +3,7 @@ """ import argparse +import fnmatch import json import os from functools import partial @@ -251,10 +252,25 @@ def run_blockchain_test(self) -> None: def run(self) -> int: """Run the transition and provide the relevant outputs""" - # Clean out files from the output directory + # Clear files that may have been created in a previous + # run of the t8n tool. + # Define the specific files and pattern to delete + files_to_delete = [ + self.options.output_result, + self.options.output_alloc, + self.options.output_body, + ] + pattern_to_delete = "trace-*.jsonl" + + # Iterate through the directory for file in os.listdir(self.options.output_basedir): - if file.endswith(".json") or file.endswith(".jsonl"): - os.remove(os.path.join(self.options.output_basedir, file)) + file_path = os.path.join(self.options.output_basedir, file) + + # Check if the file matches the specific names or the pattern + if file in files_to_delete or fnmatch.fnmatch( + file, pattern_to_delete + ): + os.remove(file_path) try: if self.options.state_test: From 8707c283cf21154bd7b3fba2f42f186270d97927 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 8 Apr 2025 05:03:45 -0400 Subject: [PATCH 39/70] Refactored AccessListTransaction by hoisting complex class member - New Access class abstracts complex tuple of terms used by ALT - All forks which use ALT now use Access - All changes propogated to FeeMarketTransaction, BlobTransaction - Updated corresponding RLP tests - Updated docstrings - Updated t8n to comply with changes --- src/ethereum/arrow_glacier/fork.py | 8 +++--- src/ethereum/arrow_glacier/transactions.py | 22 +++++++++++++--- src/ethereum/berlin/fork.py | 8 +++--- src/ethereum/berlin/transactions.py | 20 +++++++++++--- src/ethereum/cancun/fork.py | 8 +++--- src/ethereum/cancun/transactions.py | 24 +++++++++++++---- src/ethereum/gray_glacier/fork.py | 8 +++--- src/ethereum/gray_glacier/transactions.py | 22 +++++++++++++--- src/ethereum/london/fork.py | 8 +++--- src/ethereum/london/transactions.py | 22 +++++++++++++--- src/ethereum/paris/fork.py | 8 +++--- src/ethereum/paris/transactions.py | 22 +++++++++++++--- src/ethereum/prague/fork.py | 8 +++--- src/ethereum/prague/transactions.py | 26 ++++++++++++++----- src/ethereum/shanghai/fork.py | 8 +++--- src/ethereum/shanghai/transactions.py | 22 +++++++++++++--- .../evm_tools/loaders/fork_loader.py | 5 ++++ .../evm_tools/loaders/transaction_loader.py | 2 +- tests/berlin/test_rlp.py | 6 ++++- tests/cancun/test_rlp.py | 11 ++++++-- tests/london/test_rlp.py | 11 ++++++-- tests/paris/test_rlp.py | 11 ++++++-- tests/prague/test_rlp.py | 11 ++++++-- tests/shanghai/test_rlp.py | 11 ++++++-- 24 files changed, 234 insertions(+), 78 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index a6faa5429a..b5bd754b15 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -733,10 +733,10 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index 57dbe9ec50..285a7257c0 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -195,9 +207,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 662974467f..f6c2f57442 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -634,10 +634,10 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() if isinstance(tx, AccessListTransaction): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 432d942906..0a756caad1 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -167,9 +179,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, AccessListTransaction): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index b4ae716a80..e11f0527e0 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -668,10 +668,10 @@ def process_transaction( if isinstance( tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) ): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index 8865fe846f..ae97ee26d8 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -100,7 +112,7 @@ class BlobTransaction: to: Address value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] max_fee_per_blob_gas: U256 blob_versioned_hashes: Tuple[VersionedHash, ...] y_parity: U256 @@ -234,9 +246,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: if isinstance( tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) ): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 0dee77a920..92372ef4da 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -733,10 +733,10 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index 57dbe9ec50..285a7257c0 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -195,9 +207,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index fc8ea7adbc..4b05bea0f9 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -739,10 +739,10 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index 57dbe9ec50..285a7257c0 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -195,9 +207,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index 714a68864e..43c27acc71 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -512,10 +512,10 @@ def process_transaction( access_list_addresses = set() access_list_storage_keys = set() if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 57dbe9ec50..285a7257c0 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -195,9 +207,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 1058b7c288..0ec47ccd2f 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -762,10 +762,10 @@ def process_transaction( SetCodeTransaction, ), ): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) authorizations: Tuple[Authorization, ...] = () if isinstance(tx, SetCodeTransaction): diff --git a/src/ethereum/prague/transactions.py b/src/ethereum/prague/transactions.py index 3e5adcfb46..d88cdcea00 100644 --- a/src/ethereum/prague/transactions.py +++ b/src/ethereum/prague/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -100,7 +112,7 @@ class BlobTransaction: to: Address value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] max_fee_per_blob_gas: U256 blob_versioned_hashes: Tuple[VersionedHash, ...] y_parity: U256 @@ -123,7 +135,7 @@ class SetCodeTransaction: to: Address value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] authorizations: Tuple[Authorization, ...] y_parity: U256 r: U256 @@ -279,9 +291,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: SetCodeTransaction, ), ): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) auth_cost = Uint(0) if isinstance(tx, SetCodeTransaction): diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 0dbf404b78..3ff1dd82a4 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -525,10 +525,10 @@ def process_transaction( access_list_storage_keys = set() access_list_addresses.add(block_env.coinbase) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) tx_env = vm.TransactionEnvironment( origin=sender, diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index f7a62acf9b..ea7d657e88 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -44,6 +44,18 @@ class LegacyTransaction: s: U256 +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + @slotted_freezable @dataclass class AccessListTransaction: @@ -58,7 +70,7 @@ class AccessListTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -79,7 +91,7 @@ class FeeMarketTransaction: to: Union[Bytes0, Address] value: U256 data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + access_list: Tuple[Access, ...] y_parity: U256 r: U256 s: U256 @@ -202,9 +214,11 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: access_list_cost = Uint(0) if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: + for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) return TX_BASE_COST + data_cost + create_cost + access_list_cost diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 1a7af024a8..13904e90d5 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -192,6 +192,11 @@ def LegacyTransaction(self) -> Any: """Legacytransaction class of the fork""" return self._module("transactions").LegacyTransaction + @property + def Access(self) -> Any: + """Access class of the fork""" + return self._module("transactions").Access + @property def AccessListTransaction(self) -> Any: """Access List transaction class of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py index b788601400..4a7d836664 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -78,7 +78,7 @@ def json_to_access_list(self) -> Any: access_list = [] for sublist in self.raw.get("accessLists", []): access_list.append( - ( + self.fork.Access( self.fork.hex_to_address(sublist.get("address")), [ hex_to_bytes32(key) diff --git a/tests/berlin/test_rlp.py b/tests/berlin/test_rlp.py index e853f4e577..c068736373 100644 --- a/tests/berlin/test_rlp.py +++ b/tests/berlin/test_rlp.py @@ -5,6 +5,7 @@ from ethereum.berlin.blocks import Block, Header, Log, Receipt from ethereum.berlin.transactions import ( + Access, AccessListTransaction, LegacyTransaction, Transaction, @@ -59,7 +60,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), diff --git a/tests/cancun/test_rlp.py b/tests/cancun/test_rlp.py index 6478fe25d9..df74a99b87 100644 --- a/tests/cancun/test_rlp.py +++ b/tests/cancun/test_rlp.py @@ -5,6 +5,7 @@ from ethereum.cancun.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.cancun.transactions import ( + Access, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, @@ -60,7 +61,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), @@ -75,7 +79,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), diff --git a/tests/london/test_rlp.py b/tests/london/test_rlp.py index a18856e6e7..e598e11f8b 100644 --- a/tests/london/test_rlp.py +++ b/tests/london/test_rlp.py @@ -6,6 +6,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.london.blocks import Block, Header, Log, Receipt from ethereum.london.transactions import ( + Access, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, @@ -60,7 +61,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), @@ -75,7 +79,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), diff --git a/tests/paris/test_rlp.py b/tests/paris/test_rlp.py index 33497fde81..3e3cb5a718 100644 --- a/tests/paris/test_rlp.py +++ b/tests/paris/test_rlp.py @@ -6,6 +6,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.paris.blocks import Block, Header, Log, Receipt from ethereum.paris.transactions import ( + Access, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, @@ -60,7 +61,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), @@ -75,7 +79,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), diff --git a/tests/prague/test_rlp.py b/tests/prague/test_rlp.py index 2dc25b675d..a305716436 100644 --- a/tests/prague/test_rlp.py +++ b/tests/prague/test_rlp.py @@ -6,6 +6,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.prague.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.prague.transactions import ( + Access, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, @@ -61,7 +62,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), @@ -76,7 +80,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), diff --git a/tests/shanghai/test_rlp.py b/tests/shanghai/test_rlp.py index 7a7d689ef6..d3046c3710 100644 --- a/tests/shanghai/test_rlp.py +++ b/tests/shanghai/test_rlp.py @@ -6,6 +6,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.shanghai.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.shanghai.transactions import ( + Access, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, @@ -60,7 +61,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), @@ -75,7 +79,10 @@ Bytes0(), U256(4), Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), U256(27), U256(5), U256(6), From d9a7ee24db359aacecd636349b4f3ac95a4a6e71 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:22:22 +0200 Subject: [PATCH 40/70] Use py_ecc for alt_bn128 (#1187) * use py_ecc for alt_bn128 * Remove unused crypto code --------- Co-authored-by: Peter Miller --- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- src/ethereum/byzantium/vm/exceptions.py | 8 + .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- src/ethereum/constantinople/vm/exceptions.py | 8 + .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- src/ethereum/crypto/alt_bn128.py | 195 --------- src/ethereum/crypto/elliptic_curve.py | 112 ----- src/ethereum/crypto/finite_field.py | 403 ------------------ .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- .../vm/precompiled_contracts/alt_bn128.py | 211 ++++++--- 17 files changed, 1720 insertions(+), 1538 deletions(-) delete mode 100644 src/ethereum/crypto/alt_bn128.py delete mode 100644 src/ethereum/crypto/finite_field.py diff --git a/src/ethereum/arrow_glacier/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/arrow_glacier/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/arrow_glacier/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/arrow_glacier/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/berlin/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/berlin/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/berlin/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/berlin/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/byzantium/vm/exceptions.py b/src/ethereum/byzantium/vm/exceptions.py index 116d4b93cf..789d302089 100644 --- a/src/ethereum/byzantium/vm/exceptions.py +++ b/src/ethereum/byzantium/vm/exceptions.py @@ -108,6 +108,14 @@ class OutOfBoundsRead(ExceptionalHalt): pass +class InvalidParameter(ExceptionalHalt): + """ + Raised when invalid parameters are passed. + """ + + pass + + class AddressCollision(ExceptionalHalt): """ Raised when the new contract address has a collision. diff --git a/src/ethereum/byzantium/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/byzantium/vm/precompiled_contracts/alt_bn128.py index 7640bf8da6..5c07e9e166 100644 --- a/src/ethereum/byzantium/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/byzantium/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(500)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(40000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/cancun/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/cancun/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/cancun/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/cancun/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/constantinople/vm/exceptions.py b/src/ethereum/constantinople/vm/exceptions.py index 116d4b93cf..789d302089 100644 --- a/src/ethereum/constantinople/vm/exceptions.py +++ b/src/ethereum/constantinople/vm/exceptions.py @@ -108,6 +108,14 @@ class OutOfBoundsRead(ExceptionalHalt): pass +class InvalidParameter(ExceptionalHalt): + """ + Raised when invalid parameters are passed. + """ + + pass + + class AddressCollision(ExceptionalHalt): """ Raised when the new contract address has a collision. diff --git a/src/ethereum/constantinople/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/constantinople/vm/precompiled_contracts/alt_bn128.py index 7640bf8da6..5c07e9e166 100644 --- a/src/ethereum/constantinople/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/constantinople/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(500)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(40000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/crypto/alt_bn128.py b/src/ethereum/crypto/alt_bn128.py deleted file mode 100644 index f971b55de0..0000000000 --- a/src/ethereum/crypto/alt_bn128.py +++ /dev/null @@ -1,195 +0,0 @@ -""" -The alt_bn128 curve -^^^^^^^^^^^^^^^^^^^ -""" - -from . import elliptic_curve, finite_field - -ALT_BN128_PRIME = 21888242871839275222246405745257275088696311157297823662689037894645226208583 # noqa: E501 -ALT_BN128_CURVE_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617 # noqa: E501 -ATE_PAIRING_COUNT = 29793968203157093289 -ATE_PAIRING_COUNT_BITS = 63 - - -class BNF(finite_field.PrimeField): - """ - The prime field over which the alt_bn128 curve is defined. - """ - - PRIME = ALT_BN128_PRIME - - -class BNP(elliptic_curve.EllipticCurve): - """ - The alt_bn128 curve. - """ - - FIELD = BNF - A = BNF(0) - B = BNF(3) - - -class BNF2(finite_field.GaloisField): - """ - `BNF` extended with a square root of 1 (`i`). - """ - - PRIME = ALT_BN128_PRIME - MODULUS = (1, 0) - - i: "BNF2" - i_plus_9: "BNF2" - - -BNF2.FROBENIUS_COEFFICIENTS = BNF2.calculate_frobenius_coefficients() -"""autoapi_noindex""" - -BNF2.i = BNF2((0, 1)) -"""autoapi_noindex""" - -BNF2.i_plus_9 = BNF2((9, 1)) -"""autoapi_noindex""" - - -class BNP2(elliptic_curve.EllipticCurve): - """ - A twist of `BNP`. This is actually the same curve as `BNP` under a change - of variable, but that change of variable is only possible over the larger - field `BNP12`. - """ - - FIELD = BNF2 - A = BNF2.zero() - B = BNF2.from_int(3) / (BNF2.i + BNF2.from_int(9)) - - -class BNF12(finite_field.GaloisField): - """ - `BNF2` extended by adding a 6th root of `9 + i` called `w` (omega). - """ - - PRIME = ALT_BN128_PRIME - MODULUS = (82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0) - - w: "BNF12" - i_plus_9: "BNF12" - - def __mul__(self: "BNF12", right: "BNF12") -> "BNF12": # type: ignore[override] # noqa: E501 - """ - Multiplication special cased for BNF12. - """ - mul = [0] * 23 - - for i in range(12): - for j in range(12): - mul[i + j] += self[i] * right[j] - - for i in range(22, 11, -1): - mul[i - 6] -= mul[i] * (-18) - mul[i - 12] -= mul[i] * 82 - - return BNF12.__new__( - BNF12, - mul[:12], - ) - - -BNF12.FROBENIUS_COEFFICIENTS = BNF12.calculate_frobenius_coefficients() -"""autoapi_noindex""" - -BNF12.w = BNF12((0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) -"""autoapi_noindex""" - -BNF12.i_plus_9 = BNF12.w**6 -"""autoapi_noindex""" - - -class BNP12(elliptic_curve.EllipticCurve): - """ - The same curve as `BNP`, but defined over the larger field. This curve has - both subgroups of order `ALT_BN128_CURVE_ORDER` and allows pairings to be - computed. - """ - - FIELD = BNF12 - A = BNF12.zero() - B = BNF12.from_int(3) - - -def bnf2_to_bnf12(x: BNF2) -> BNF12: - """ - Lift a field element in `BNF2` to `BNF12`. - """ - return BNF12.from_int(x[0]) + BNF12.from_int(x[1]) * ( - BNF12.i_plus_9 - BNF12.from_int(9) - ) - - -def bnp_to_bnp12(p: BNP) -> BNP12: - """ - Lift a point from `BNP` to `BNP12`. - """ - return BNP12(BNF12.from_int(int(p.x)), BNF12.from_int(int(p.y))) - - -def twist(p: BNP2) -> BNP12: - """ - Apply to twist to change variables from the curve `BNP2` to `BNP12`. - """ - return BNP12( - bnf2_to_bnf12(p.x) * (BNF12.w**2), - bnf2_to_bnf12(p.y) * (BNF12.w**3), - ) - - -def linefunc(p1: BNP12, p2: BNP12, t: BNP12) -> BNF12: - """ - Evaluate the function defining the line between points `p1` and `p2` at the - point `t`. The mathematical significance of this function is that is has - divisor `(p1) + (p2) + (p1 + p2) - 3(O)`. - - Note: Abstract mathematical presentations of Miller's algorithm often - specify the divisor `(p1) + (p2) - (p1 + p2) - (O)`. This turns out not to - matter. - """ - if p1.x != p2.x: - lam = (p2.y - p1.y) / (p2.x - p1.x) - return lam * (t.x - p1.x) - (t.y - p1.y) - elif p1.y == p2.y: - lam = BNF12.from_int(3) * p1.x**2 / (BNF12.from_int(2) * p1.y) - return lam * (t.x - p1.x) - (t.y - p1.y) - else: - return t.x - p1.x - - -def miller_loop(q: BNP12, p: BNP12) -> BNF12: - """ - The core of the pairing algorithm. - """ - if p == BNP12.point_at_infinity() or q == BNP12.point_at_infinity(): - return BNF12.from_int(1) - r = q - f = BNF12.from_int(1) - for i in range(ATE_PAIRING_COUNT_BITS, -1, -1): - f = f * f * linefunc(r, r, p) - r = r.double() - if (ATE_PAIRING_COUNT - 1) & (2**i): - f = f * linefunc(r, q, p) - r = r + q - assert r == q.mul_by(ATE_PAIRING_COUNT - 1) - - q1 = BNP12(q.x.frobenius(), q.y.frobenius()) - nq2 = BNP12(q1.x.frobenius(), -q1.y.frobenius()) - - f = f * linefunc(r, q1, p) - r = r + q1 - f = f * linefunc(r, nq2, p) - - return f ** ((ALT_BN128_PRIME**12 - 1) // ALT_BN128_CURVE_ORDER) - - -def pairing(q: BNP2, p: BNP) -> BNF12: - """ - Compute the pairing of `q` and `p`. - """ - return miller_loop(twist(q), bnp_to_bnp12(p)) diff --git a/src/ethereum/crypto/elliptic_curve.py b/src/ethereum/crypto/elliptic_curve.py index 76970900f2..8a6c0e1764 100644 --- a/src/ethereum/crypto/elliptic_curve.py +++ b/src/ethereum/crypto/elliptic_curve.py @@ -3,15 +3,12 @@ ^^^^^^^^^^^^^^^ """ -from typing import Generic, Type, TypeVar - import coincurve from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256 from ethereum.exceptions import InvalidSignatureError -from .finite_field import Field from .hash import Hash32 SECP256K1B = U256(7) @@ -22,9 +19,6 @@ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 ) -F = TypeVar("F", bound=Field) -T = TypeVar("T", bound="EllipticCurve") - def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes: """ @@ -77,109 +71,3 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes: public_key = public_key.format(compressed=False)[1:] return public_key - - -class EllipticCurve(Generic[F]): - """ - Superclass for integers modulo a prime. Not intended to be used - directly, but rather to be subclassed. - """ - - __slots__ = ("x", "y") - - FIELD: Type[F] - A: F - B: F - - x: F - y: F - - def __new__(cls: Type[T], x: F, y: F) -> T: - """ - Make new point on the curve. The point is not checked to see if it is - on the curve. - """ - res = object.__new__(cls) - res.x = x - res.y = y - return res - - def __init__(self, x: F, y: F) -> None: - """ - Checks if the point is on the curve. To skip this check call - `__new__()` directly. - """ - if ( - x != self.FIELD.zero() or y != self.FIELD.zero() - ) and y ** 2 - x**3 - self.A * x - self.B != self.FIELD.zero(): - raise ValueError("Point not on curve") - - def __eq__(self, other: object) -> bool: - """ - Test two points for equality. - """ - if not isinstance(other, type(self)): - return False - return self.x == other.x and self.y == other.y - - def __str__(self) -> str: - """ - Stringify a point as its coordinates. - """ - return str((self.x, self.y)) - - @classmethod - def point_at_infinity(cls: Type[T]) -> T: - """ - Return the point at infinity. This is the identity element of the group - operation. - - The point at infinity doesn't actually have coordinates so we use - `(0, 0)` (which isn't on the curve) to represent it. - """ - return cls.__new__(cls, cls.FIELD.zero(), cls.FIELD.zero()) - - def double(self: T) -> T: - """ - Add a point to itself. - """ - x, y, F = self.x, self.y, self.FIELD - if x == 0 and y == 0: - return self - lam = (F.from_int(3) * x**2 + self.A) / (F.from_int(2) * y) - new_x = lam**2 - x - x - new_y = lam * (x - new_x) - y - return self.__new__(type(self), new_x, new_y) - - def __add__(self: T, other: T) -> T: - """ - Add two points together. - """ - ZERO = self.FIELD.zero() - self_x, self_y, other_x, other_y = self.x, self.y, other.x, other.y - if self_x == ZERO and self_y == ZERO: - return other - if other_x == ZERO and other_y == ZERO: - return self - if self_x == other_x: - if self_y == other_y: - return self.double() - else: - return self.point_at_infinity() - lam = (other_y - self_y) / (other_x - self_x) - x = lam**2 - self_x - other_x - y = lam * (self_x - x) - self_y - return self.__new__(type(self), x, y) - - def mul_by(self: T, n: int) -> T: - """ - Multiply `self` by `n` using the double and add algorithm. - """ - res = self.__new__(type(self), self.FIELD.zero(), self.FIELD.zero()) - s = self - while n != 0: - if n % 2 == 1: - res = res + s - s = s + s - n //= 2 - return res diff --git a/src/ethereum/crypto/finite_field.py b/src/ethereum/crypto/finite_field.py deleted file mode 100644 index 1e1fc6d5df..0000000000 --- a/src/ethereum/crypto/finite_field.py +++ /dev/null @@ -1,403 +0,0 @@ -""" -Finite Fields -^^^^^^^^^^^^^ -""" - -# flake8: noqa: D102, D105 - -from typing import Iterable, List, Tuple, Type, TypeVar, cast - -from ethereum_types.bytes import Bytes, Bytes32 -from typing_extensions import Protocol - -F = TypeVar("F", bound="Field") - - -class Field(Protocol): - """ - A type protocol for defining fields. - """ - - __slots__ = () - - @classmethod - def zero(cls: Type[F]) -> F: - ... - - @classmethod - def from_int(cls: Type[F], n: int) -> F: - ... - - def __radd__(self: F, left: F) -> F: - ... - - def __add__(self: F, right: F) -> F: - ... - - def __iadd__(self: F, right: F) -> F: - ... - - def __sub__(self: F, right: F) -> F: - ... - - def __rsub__(self: F, left: F) -> F: - ... - - def __mul__(self: F, right: F) -> F: - ... - - def __rmul__(self: F, left: F) -> F: - ... - - def __imul__(self: F, right: F) -> F: - ... - - def __pow__(self: F, exponent: int) -> F: - ... - - def __ipow__(self: F, right: int) -> F: - ... - - def __neg__(self: F) -> F: - ... - - def __truediv__(self: F, right: F) -> F: - ... - - -T = TypeVar("T", bound="PrimeField") - - -class PrimeField(int, Field): - """ - Superclass for integers modulo a prime. Not intended to be used - directly, but rather to be subclassed. - """ - - __slots__ = () - PRIME: int - - @classmethod - def from_be_bytes(cls: Type[T], buffer: "Bytes") -> T: - """ - Converts a sequence of bytes into a element of the field. - Parameters - ---------- - buffer : - Bytes to decode. - Returns - ------- - self : `T` - Unsigned integer decoded from `buffer`. - """ - return cls(int.from_bytes(buffer, "big")) - - @classmethod - def zero(cls: Type[T]) -> T: - return cls.__new__(cls, 0) - - @classmethod - def from_int(cls: Type[T], n: int) -> T: - return cls(n) - - def __new__(cls: Type[T], value: int) -> T: - return int.__new__(cls, value % cls.PRIME) - - def __radd__(self: T, left: T) -> T: # type: ignore[override] - return self.__add__(left) - - def __add__(self: T, right: T) -> T: # type: ignore[override] - if not isinstance(right, int): - return NotImplemented - - return self.__new__(type(self), int.__add__(self, right)) - - def __iadd__(self: T, right: T) -> T: # type: ignore[override] - return self.__add__(right) - - def __sub__(self: T, right: T) -> T: # type: ignore[override] - if not isinstance(right, int): - return NotImplemented - - return self.__new__(type(self), int.__sub__(self, right)) - - def __rsub__(self: T, left: T) -> T: # type: ignore[override] - if not isinstance(left, int): - return NotImplemented - - return self.__new__(type(self), int.__rsub__(self, left)) - - def __mul__(self: T, right: T) -> T: # type: ignore[override] - if not isinstance(right, int): - return NotImplemented - - return self.__new__(type(self), int.__mul__(self, right)) - - def __rmul__(self: T, left: T) -> T: # type: ignore[override] - return self.__mul__(left) - - def __imul__(self: T, right: T) -> T: # type: ignore[override] - return self.__mul__(right) - - __floordiv__ = None # type: ignore - __rfloordiv__ = None # type: ignore - __ifloordiv__ = None - __divmod__ = None # type: ignore - __rdivmod__ = None # type: ignore - - def __pow__(self: T, exponent: int) -> T: # type: ignore[override] - # For reasons that are unclear, self must be cast to int here under - # PyPy. - return self.__new__( - type(self), int.__pow__(int(self), exponent, self.PRIME) - ) - - __rpow__ = None # type: ignore - - def __ipow__(self: T, right: int) -> T: # type: ignore[override] - return self.__pow__(right) - - __and__ = None # type: ignore - __or__ = None # type: ignore - __xor__ = None # type: ignore - __rxor__ = None # type: ignore - __ixor__ = None - __rshift__ = None # type: ignore - __lshift__ = None # type: ignore - __irshift__ = None - __ilshift__ = None - - def __neg__(self: T) -> T: - return self.__new__(type(self), int.__neg__(self)) - - def __truediv__(self: T, right: T) -> T: # type: ignore[override] - return self * right.multiplicative_inverse() - - def multiplicative_inverse(self: T) -> T: - return self ** (-1) - - def to_be_bytes32(self) -> "Bytes32": - """ - Converts this arbitrarily sized unsigned integer into its big endian - representation with exactly 32 bytes. - Returns - ------- - big_endian : `Bytes32` - Big endian (most significant bits first) representation. - """ - return Bytes32(self.to_bytes(32, "big")) - - -U = TypeVar("U", bound="GaloisField") - - -class GaloisField(tuple, Field): - """ - Superclass for defining finite fields. Not intended to be used - directly, but rather to be subclassed. - - Fields are represented as `F_p[x]/(x^n + ...)` where the `MODULUS` is a - tuple of the non-leading coefficients of the defining polynomial. For - example `x^3 + 2x^2 + 3x + 4` is `(2, 3, 4)`. - - In practice the polynomial is likely to be sparse and you should overload - the `__mul__()` function to take advantage of this fact. - """ - - __slots__ = () - - PRIME: int - MODULUS: Tuple[int, ...] - FROBENIUS_COEFFICIENTS: Tuple["GaloisField", ...] - - @classmethod - def zero(cls: Type[U]) -> U: - return cls.__new__(cls, [0] * len(cls.MODULUS)) - - @classmethod - def from_int(cls: Type[U], n: int) -> U: - return cls.__new__(cls, [n] + [0] * (len(cls.MODULUS) - 1)) - - def __new__(cls: Type[U], iterable: Iterable[int]) -> U: - self = tuple.__new__(cls, (x % cls.PRIME for x in iterable)) - assert len(self) == len(cls.MODULUS) - return self - - def __add__(self: U, right: U) -> U: # type: ignore[override] - if not isinstance(right, type(self)): - return NotImplemented - - return self.__new__( - type(self), - ( - x + y - for (x, y) in cast(Iterable[Tuple[int, int]], zip(self, right)) - ), - ) - - def __radd__(self: U, left: U) -> U: - return self.__add__(left) - - def __iadd__(self: U, right: U) -> U: # type: ignore[override] - return self.__add__(right) - - def __sub__(self: U, right: U) -> U: - if not isinstance(right, type(self)): - return NotImplemented - - x: int - y: int - return self.__new__( - type(self), - ( - x - y - for (x, y) in cast(Iterable[Tuple[int, int]], zip(self, right)) - ), - ) - - def __rsub__(self: U, left: U) -> U: - if not isinstance(left, type(self)): - return NotImplemented - - return self.__new__( - type(self), - ( - x - y - for (x, y) in cast(Iterable[Tuple[int, int]], zip(left, self)) - ), - ) - - def __mul__(self: U, right: U) -> U: # type: ignore[override] - modulus = self.MODULUS - degree = len(modulus) - prime = self.PRIME - mul = [0] * (degree * 2) - - for i in range(degree): - for j in range(degree): - mul[i + j] += self[i] * right[j] - - for i in range(degree * 2 - 1, degree - 1, -1): - for j in range(i - degree, i): - mul[j] -= (mul[i] * modulus[degree - (i - j)]) % prime - - return self.__new__( - type(self), - mul[:degree], - ) - - def __rmul__(self: U, left: U) -> U: # type: ignore[override] - return self.__mul__(left) - - def __imul__(self: U, right: U) -> U: # type: ignore[override] - return self.__mul__(right) - - def __truediv__(self: U, right: U) -> U: - return self * right.multiplicative_inverse() - - def __neg__(self: U) -> U: - return self.__new__(type(self), (-a for a in self)) - - def scalar_mul(self: U, x: int) -> U: - """ - Multiply a field element by a integer. This is faster than using - `from_int()` and field multiplication. - """ - return self.__new__(type(self), (x * n for n in self)) - - def deg(self: U) -> int: - """ - This is a support function for `multiplicative_inverse()`. - """ - for i in range(len(self.MODULUS) - 1, -1, -1): - if self[i] != 0: - return i - raise ValueError("deg() does not support zero") - - def multiplicative_inverse(self: U) -> U: - """ - Calculate the multiplicative inverse. Uses the Euclidean algorithm. - """ - x2: List[int] - p = self.PRIME - x1, f1 = list(self.MODULUS), [0] * len(self) - x2, f2, d2 = list(self), [1] + [0] * (len(self) - 1), self.deg() - q_0 = pow(x2[d2], -1, p) - for i in range(d2): - x1[i + len(x1) - d2] = (x1[i + len(x1) - d2] - q_0 * x2[i]) % p - f1[i + len(x1) - d2] = (f1[i + len(x1) - d2] - q_0 * f2[i]) % p - for i in range(len(self.MODULUS) - 1, -1, -1): - if x1[i] != 0: - d1 = i - break - while True: - if d1 == 0: - ans = f1 - q = pow(x1[0], -1, self.PRIME) - for i in range(len(ans)): - ans[i] *= q - break - elif d2 == 0: - ans = f2 - q = pow(x2[0], -1, self.PRIME) - for i in range(len(ans)): - ans *= q - break - if d1 < d2: - q = x2[d2] * pow(x1[d1], -1, self.PRIME) - for i in range(len(self.MODULUS) - (d2 - d1)): - x2[i + (d2 - d1)] = (x2[i + (d2 - d1)] - q * x1[i]) % p - f2[i + (d2 - d1)] = (f2[i + (d2 - d1)] - q * f1[i]) % p - while x2[d2] == 0: - d2 -= 1 - else: - q = x1[d1] * pow(x2[d2], -1, self.PRIME) - for i in range(len(self.MODULUS) - (d1 - d2)): - x1[i + (d1 - d2)] = (x1[i + (d1 - d2)] - q * x2[i]) % p - f1[i + (d1 - d2)] = (f1[i + (d1 - d2)] - q * f2[i]) % p - while x1[d1] == 0: - d1 -= 1 - return self.__new__(type(self), ans) - - def __pow__(self: U, exponent: int) -> U: - degree = len(self.MODULUS) - if exponent < 0: - self = self.multiplicative_inverse() - exponent = -exponent - - res = self.__new__(type(self), [1] + [0] * (degree - 1)) - s = self - while exponent != 0: - if exponent % 2 == 1: - res *= s - s *= s - exponent //= 2 - return res - - def __ipow__(self: U, right: int) -> U: - return self.__pow__(right) - - @classmethod - def calculate_frobenius_coefficients(cls: Type[U]) -> Tuple[U, ...]: - """ - Calculate the coefficients needed by `frobenius()`. - """ - coefficients = [] - for i in range(len(cls.MODULUS)): - x = [0] * len(cls.MODULUS) - x[i] = 1 - coefficients.append(cls.__new__(cls, x) ** cls.PRIME) - return tuple(coefficients) - - def frobenius(self: U) -> U: - """ - Returns `self ** p`. This function is known as the Frobenius - endomorphism and has many special mathematical properties. In - particular it is extremely cheap to compute compared to other - exponentiations. - """ - ans = self.from_int(0) - a: int - for i, a in enumerate(self): - ans += cast(U, self.FROBENIUS_COEFFICIENTS[i]).scalar_mul(a) - return ans diff --git a/src/ethereum/gray_glacier/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/gray_glacier/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/gray_glacier/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/gray_glacier/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/istanbul/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/istanbul/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/istanbul/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/istanbul/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/london/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/london/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/london/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/london/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/muir_glacier/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/muir_glacier/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/muir_glacier/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/muir_glacier/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/paris/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/paris/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/paris/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/paris/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/prague/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/shanghai/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/shanghai/vm/precompiled_contracts/alt_bn128.py index dc75b40ac6..78490cf2c5 100644 --- a/src/ethereum/shanghai/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/shanghai/vm/precompiled_contracts/alt_bn128.py @@ -11,23 +11,123 @@ Implementation of the ALT_BN128 precompiled contracts. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, ) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D from ...vm import Evm from ...vm.gas import charge_gas from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point def alt_bn128_add(evm: Evm) -> None: @@ -45,28 +145,19 @@ def alt_bn128_add(evm: Evm) -> None: charge_gas(evm, Uint(150)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_mul(evm: Evm) -> None: @@ -84,24 +175,19 @@ def alt_bn128_mul(evm: Evm) -> None: charge_gas(evm, Uint(6000)) # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - p = p0.mul_by(n) + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() def alt_bn128_pairing_check(evm: Evm) -> None: @@ -121,34 +207,21 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # OPERATION if len(data) % 192 != 0: raise OutOfGasError - result = BNF12.from_int(1) + result = FQ12.one() for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + if multiply(q, curve_order) is not None: raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) + if p is not None and q is not None: + result *= pairing(q, p) - if result == BNF12.from_int(1): + if result == FQ12.one(): evm.output = U256(1).to_be_bytes32() else: evm.output = U256(0).to_be_bytes32() From 4cf63ecec35af59ddf7a604778a91831f846cb6e Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Sat, 19 Apr 2025 21:39:41 +0200 Subject: [PATCH 41/70] EOAs that delegate to a precompile (#1191) * delegated to a precompile * update to eest 4.2.0 * expect variable accessList * post review update-1 change variable name from is_delegated to disable_precompiles --- src/ethereum/prague/fork.py | 1 + src/ethereum/prague/utils/message.py | 4 +++ src/ethereum/prague/vm/__init__.py | 1 + src/ethereum/prague/vm/eoa_delegation.py | 1 + src/ethereum/prague/vm/instructions/system.py | 26 ++++++++----------- src/ethereum/prague/vm/interpreter.py | 2 ++ .../evm_tools/loaders/transaction_loader.py | 4 +-- .../evm_tools/statetest/__init__.py | 2 +- tests/helpers/__init__.py | 2 +- tests/helpers/load_evm_tools_tests.py | 2 +- whitelist.txt | 3 ++- 11 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 0ec47ccd2f..03e41b2a8b 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -566,6 +566,7 @@ def process_system_transaction( is_static=False, accessed_addresses=set(), accessed_storage_keys=set(), + disable_precompiles=False, parent_evm=None, ) diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py index 9d3f615739..3c2041051c 100644 --- a/src/ethereum/prague/utils/message.py +++ b/src/ethereum/prague/utils/message.py @@ -51,6 +51,8 @@ def prepare_message( accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) accessed_addresses.update(tx_env.access_list_addresses) + disable_precompiles = False + if isinstance(tx.to, Bytes0): current_target = compute_contract_address( tx_env.origin, @@ -66,6 +68,7 @@ def prepare_message( delegated_address = get_delegated_code_address(code) if delegated_address is not None: + disable_precompiles = True accessed_addresses.add(delegated_address) code = get_account(block_env.state, delegated_address).code @@ -91,5 +94,6 @@ def prepare_message( is_static=False, accessed_addresses=accessed_addresses, accessed_storage_keys=set(tx_env.access_list_storage_keys), + disable_precompiles=disable_precompiles, parent_evm=None, ) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 9e91cebc2c..3f0ac0f845 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -131,6 +131,7 @@ class Message: is_static: bool accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] + disable_precompiles: bool parent_evm: Optional["Evm"] diff --git a/src/ethereum/prague/vm/eoa_delegation.py b/src/ethereum/prague/vm/eoa_delegation.py index bb6a7e33d9..3e7b7677ef 100644 --- a/src/ethereum/prague/vm/eoa_delegation.py +++ b/src/ethereum/prague/vm/eoa_delegation.py @@ -204,6 +204,7 @@ def set_delegation(message: Message) -> U256: message.code = get_account(state, message.code_address).code if is_valid_delegation(message.code): + message.disable_precompiles = True message.code_address = Address( message.code[EOA_DELEGATION_MARKER_LENGTH:] ) diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 7e8004b1ea..fadb0da6ad 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -133,6 +133,7 @@ def generic_create( is_static=False, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), + disable_precompiles=False, parent_evm=evm, ) child_evm = process_create_message(child_message) @@ -290,7 +291,7 @@ def generic_call( memory_output_start_position: U256, memory_output_size: U256, code: bytes, - is_delegated_code: bool, + disable_precompiles: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -307,12 +308,6 @@ def generic_call( call_data = memory_read_bytes( evm.memory, memory_input_start_position, memory_input_size ) - code = get_account(evm.message.block_env.state, code_address).code - - if is_delegated_code and len(code) == 0: - evm.gas_left += gas - push(evm.stack, U256(1)) - return child_message = Message( block_env=evm.message.block_env, @@ -330,6 +325,7 @@ def generic_call( is_static=True if is_staticcall else evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), + disable_precompiles=disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -386,7 +382,7 @@ def call(evm: Evm) -> None: code_address = to ( - is_delegated_code, + disable_precompiles, code_address, code, delegated_access_gas_cost, @@ -432,7 +428,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - is_delegated_code, + disable_precompiles, ) # PROGRAM COUNTER @@ -475,7 +471,7 @@ def callcode(evm: Evm) -> None: access_gas_cost = GAS_COLD_ACCOUNT_ACCESS ( - is_delegated_code, + disable_precompiles, code_address, code, delegated_access_gas_cost, @@ -516,7 +512,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - is_delegated_code, + disable_precompiles, ) # PROGRAM COUNTER @@ -618,7 +614,7 @@ def delegatecall(evm: Evm) -> None: access_gas_cost = GAS_COLD_ACCOUNT_ACCESS ( - is_delegated_code, + disable_precompiles, code_address, code, delegated_access_gas_cost, @@ -646,7 +642,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - is_delegated_code, + disable_precompiles, ) # PROGRAM COUNTER @@ -687,7 +683,7 @@ def staticcall(evm: Evm) -> None: code_address = to ( - is_delegated_code, + disable_precompiles, code_address, code, delegated_access_gas_cost, @@ -719,7 +715,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - is_delegated_code, + disable_precompiles, ) # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index 5856c5af9c..278c7a4491 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -300,6 +300,8 @@ def execute_code(message: Message) -> Evm: ) try: if evm.message.code_address in PRE_COMPILED_CONTRACTS: + if message.disable_precompiles: + return evm evm_trace(evm, PrecompileStart(evm.message.code_address)) PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) evm_trace(evm, PrecompileEnd()) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py index 4a7d836664..2c4e6525fa 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -76,7 +76,7 @@ def json_to_data(self) -> Bytes: def json_to_access_list(self) -> Any: """Get the access list of the transaction.""" access_list = [] - for sublist in self.raw.get("accessLists", []): + for sublist in self.raw.get("accessList", []): access_list.append( self.fork.Access( self.fork.hex_to_address(sublist.get("address")), @@ -192,7 +192,7 @@ def read(self) -> Any: elif "maxFeePerGas" in self.raw: tx_cls = self.fork.FeeMarketTransaction tx_byte_prefix = b"\x02" - elif "accessLists" in self.raw: + elif "accessList" in self.raw: tx_cls = self.fork.AccessListTransaction tx_byte_prefix = b"\x01" else: diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py index 7b42033892..6ecca69ad5 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py @@ -107,7 +107,7 @@ def run_test_case( tx[k] = value[v] elif k == "accessLists": if value[d] is not None: - tx["accessLists"] = value[d] + tx["accessList"] = value[d] else: tx[k] = value diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 42fae17f80..f968178ad2 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -13,7 +13,7 @@ }, "latest_fork_tests": { "url": "https://github.com/gurukamath/latest_fork_tests.git", - "commit_hash": "bc74af5", + "commit_hash": "7a22f8e", "fixture_path": "tests/fixtures/latest_fork_tests", }, } diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index 66d183523b..38af0eafff 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -95,7 +95,7 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: tx[k] = value[v] elif k == "accessLists": if value[d] is not None: - tx["accessLists"] = value[d] + tx["accessList"] = value[d] else: tx[k] = value diff --git a/whitelist.txt b/whitelist.txt index 74cbb64d59..9144fb4619 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -466,4 +466,5 @@ x2 eoa blockchain -listdir \ No newline at end of file +listdir +precompiles \ No newline at end of file From 5f1a9cd4f0a7c4f92d128554eef7ae9c451dd83e Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 15 Apr 2025 12:56:48 +0000 Subject: [PATCH 42/70] t8n: Return `InvalidBlock` exception in `result.blockException` --- .../evm_tools/t8n/__init__.py | 83 ++++++++++--------- .../evm_tools/t8n/t8n_types.py | 4 + 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index c9cb8e1477..93e42810d5 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -13,7 +13,7 @@ from ethereum_types.numeric import U64, Uint from ethereum import trace -from ethereum.exceptions import EthereumException +from ethereum.exceptions import EthereumException, InvalidBlock from ethereum_spec_tools.forks import Hardfork from ..loaders.fixture_loader import Load @@ -203,52 +203,55 @@ def run_blockchain_test(self) -> None: """ Apply a block on the pre-state. Also includes system operations. """ - block_env = self.block_environment() - block_output = self.fork.BlockOutput() + try: + block_env = self.block_environment() + block_output = self.fork.BlockOutput() - if self.fork.is_after_fork("ethereum.prague"): - self.fork.process_system_transaction( - block_env=block_env, - target_address=self.fork.HISTORY_STORAGE_ADDRESS, - data=block_env.block_hashes[-1], # The parent hash - ) + if self.fork.is_after_fork("ethereum.prague"): + self.fork.process_system_transaction( + block_env=block_env, + target_address=self.fork.HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash + ) - if self.fork.is_after_fork("ethereum.cancun"): - self.fork.process_system_transaction( - block_env=block_env, - target_address=self.fork.BEACON_ROOTS_ADDRESS, - data=block_env.parent_beacon_block_root, - ) + if self.fork.is_after_fork("ethereum.cancun"): + self.fork.process_system_transaction( + block_env=block_env, + target_address=self.fork.BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, + ) - for i, tx in zip(self.txs.successfully_parsed, self.txs.transactions): - self.backup_state() - try: - self.fork.process_transaction( - block_env, block_output, tx, Uint(i) + for i, tx in zip(self.txs.successfully_parsed, self.txs.transactions): + self.backup_state() + try: + self.fork.process_transaction( + block_env, block_output, tx, Uint(i) + ) + except EthereumException as e: + self.txs.rejected_txs[i] = f"Failed transaction: {e!r}" + self.restore_state() + self.logger.warning(f"Transaction {i} failed: {e!r}") + + if not self.fork.is_after_fork("ethereum.paris"): + self.fork.pay_rewards( + block_env.state, + block_env.number, + block_env.coinbase, + self.env.ommers, ) - except EthereumException as e: - self.txs.rejected_txs[i] = f"Failed transaction: {e!r}" - self.restore_state() - self.logger.warning(f"Transaction {i} failed: {e!r}") - - if not self.fork.is_after_fork("ethereum.paris"): - self.fork.pay_rewards( - block_env.state, - block_env.number, - block_env.coinbase, - self.env.ommers, - ) - if self.fork.is_after_fork("ethereum.shanghai"): - self.fork.process_withdrawals( - block_env, block_output, self.env.withdrawals - ) + if self.fork.is_after_fork("ethereum.shanghai"): + self.fork.process_withdrawals( + block_env, block_output, self.env.withdrawals + ) - if self.fork.is_after_fork("ethereum.prague"): - self.fork.process_general_purpose_requests(block_env, block_output) + if self.fork.is_after_fork("ethereum.prague"): + self.fork.process_general_purpose_requests(block_env, block_output) - self.result.update(self, block_env, block_output) - self.result.rejected = self.txs.rejected_txs + self.result.update(self, block_env, block_output) + self.result.rejected = self.txs.rejected_txs + except InvalidBlock as e: + self.result.block_exception = f"{e}" def run(self) -> int: """Run the transition and provide the relevant outputs""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 108e41d09c..431e204354 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -267,6 +267,7 @@ class Result: blob_gas_used: Optional[Uint] = None requests_hash: Optional[Hash32] = None requests: Optional[List[Bytes]] = None + block_exception: Optional[str] = None def get_receipts_from_tries( self, t8n: Any, tx_trie: Any, receipts_trie: Any @@ -372,5 +373,8 @@ def to_json(self) -> Any: # T8N doesn't consider the request type byte to be part of the # request data["requests"] = [encode_to_hex(req) for req in self.requests] + + if self.block_exception is not None: + data["blockException"] = self.block_exception return data From e3d0743a8c9e205da76e26de43605e819568255d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 15 Apr 2025 14:13:39 +0000 Subject: [PATCH 43/70] fix: Update result on error --- src/ethereum_spec_tools/evm_tools/t8n/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 93e42810d5..d65a64b1de 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -248,10 +248,11 @@ def run_blockchain_test(self) -> None: if self.fork.is_after_fork("ethereum.prague"): self.fork.process_general_purpose_requests(block_env, block_output) - self.result.update(self, block_env, block_output) - self.result.rejected = self.txs.rejected_txs except InvalidBlock as e: self.result.block_exception = f"{e}" + + self.result.update(self, block_env, block_output) + self.result.rejected = self.txs.rejected_txs def run(self) -> int: """Run the transition and provide the relevant outputs""" From 9a6b298d4801b732a9cad6ac817981cf603bde11 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 22 Apr 2025 17:56:24 -0400 Subject: [PATCH 44/70] Split run_blockchain_test in two --- .../evm_tools/t8n/__init__.py | 88 ++++++++++--------- .../evm_tools/t8n/t8n_types.py | 2 +- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index d65a64b1de..21bc7b18f6 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -199,58 +199,60 @@ def run_state_test(self) -> Any: self.result.update(self, block_env, block_output) self.result.rejected = self.txs.rejected_txs - def run_blockchain_test(self) -> None: - """ - Apply a block on the pre-state. Also includes system operations. - """ - try: - block_env = self.block_environment() - block_output = self.fork.BlockOutput() + def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: + if self.fork.is_after_fork("ethereum.prague"): + self.fork.process_system_transaction( + block_env=block_env, + target_address=self.fork.HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash + ) - if self.fork.is_after_fork("ethereum.prague"): - self.fork.process_system_transaction( - block_env=block_env, - target_address=self.fork.HISTORY_STORAGE_ADDRESS, - data=block_env.block_hashes[-1], # The parent hash - ) + if self.fork.is_after_fork("ethereum.cancun"): + self.fork.process_system_transaction( + block_env=block_env, + target_address=self.fork.BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, + ) - if self.fork.is_after_fork("ethereum.cancun"): - self.fork.process_system_transaction( - block_env=block_env, - target_address=self.fork.BEACON_ROOTS_ADDRESS, - data=block_env.parent_beacon_block_root, + for i, tx in zip(self.txs.successfully_parsed, self.txs.transactions): + self.backup_state() + try: + self.fork.process_transaction( + block_env, block_output, tx, Uint(i) ) + except EthereumException as e: + self.txs.rejected_txs[i] = f"Failed transaction: {e!r}" + self.restore_state() + self.logger.warning(f"Transaction {i} failed: {e!r}") + + if not self.fork.is_after_fork("ethereum.paris"): + self.fork.pay_rewards( + block_env.state, + block_env.number, + block_env.coinbase, + self.env.ommers, + ) - for i, tx in zip(self.txs.successfully_parsed, self.txs.transactions): - self.backup_state() - try: - self.fork.process_transaction( - block_env, block_output, tx, Uint(i) - ) - except EthereumException as e: - self.txs.rejected_txs[i] = f"Failed transaction: {e!r}" - self.restore_state() - self.logger.warning(f"Transaction {i} failed: {e!r}") - - if not self.fork.is_after_fork("ethereum.paris"): - self.fork.pay_rewards( - block_env.state, - block_env.number, - block_env.coinbase, - self.env.ommers, - ) + if self.fork.is_after_fork("ethereum.shanghai"): + self.fork.process_withdrawals( + block_env, block_output, self.env.withdrawals + ) - if self.fork.is_after_fork("ethereum.shanghai"): - self.fork.process_withdrawals( - block_env, block_output, self.env.withdrawals - ) + if self.fork.is_after_fork("ethereum.prague"): + self.fork.process_general_purpose_requests(block_env, block_output) - if self.fork.is_after_fork("ethereum.prague"): - self.fork.process_general_purpose_requests(block_env, block_output) + def run_blockchain_test(self) -> None: + """ + Apply a block on the pre-state. Also includes system operations. + """ + block_env = self.block_environment() + block_output = self.fork.BlockOutput() + try: + self._run_blockchain_test(block_env, block_output) except InvalidBlock as e: self.result.block_exception = f"{e}" - + self.result.update(self, block_env, block_output) self.result.rejected = self.txs.rejected_txs diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 431e204354..505facd7d1 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -373,7 +373,7 @@ def to_json(self) -> Any: # T8N doesn't consider the request type byte to be part of the # request data["requests"] = [encode_to_hex(req) for req in self.requests] - + if self.block_exception is not None: data["blockException"] = self.block_exception From 936ec948a6b51011fc984344ee617a78e8b403d7 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 22 Apr 2025 19:27:54 -0400 Subject: [PATCH 45/70] Bump version --- src/ethereum/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum/__init__.py b/src/ethereum/__init__.py index 8b5958b584..c84faca9a8 100644 --- a/src/ethereum/__init__.py +++ b/src/ethereum/__init__.py @@ -18,7 +18,7 @@ """ import sys -__version__ = "1.17.0rc6.dev1" +__version__ = "1.17.0rc6" # # Ensure we can reach 1024 frames of recursion From b8fd8aab5d024c6549f58cadda1a6aabbdb0f3d8 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 17 Apr 2025 13:05:49 +0200 Subject: [PATCH 46/70] process deposit requests after txs --- src/ethereum/prague/fork.py | 10 +++++----- src/ethereum/prague/requests.py | 25 +++++++++++++------------ src/ethereum/prague/vm/__init__.py | 4 +++- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 03e41b2a8b..2463b10565 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -35,7 +35,7 @@ DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, compute_requests_hash, - parse_deposit_requests_from_receipt, + parse_deposit_requests_from_receipts, ) from .state import ( State, @@ -655,7 +655,9 @@ def process_general_purpose_requests( The block output for the current block. """ # Requests are to be in ascending order of request type - deposit_requests = block_output.deposit_requests + deposit_requests = parse_deposit_requests_from_receipts( + block_output.receipts + ) requests_from_execution = block_output.requests if len(deposit_requests) > 0: requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) @@ -848,9 +850,7 @@ def process_transaction( block_output.block_logs += tx_output.logs - block_output.deposit_requests += parse_deposit_requests_from_receipt( - receipt - ) + block_output.receipts += (receipt,) def process_withdrawals( diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index 129f1e6c2c..46581e0cb3 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -9,7 +9,7 @@ """ from hashlib import sha256 -from typing import List, Union +from typing import List, Tuple, Union from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen @@ -146,22 +146,23 @@ def extract_deposit_data(data: Bytes) -> Bytes: return pubkey + withdrawal_credentials + amount + signature + index -def parse_deposit_requests_from_receipt( - receipt: Union[Bytes, Receipt], +def parse_deposit_requests_from_receipts( + receipts: Tuple[Union[Bytes, Receipt], ...], ) -> Bytes: """ Parse deposit requests from a receipt. """ deposit_requests: Bytes = b"" - decoded_receipt = decode_receipt(receipt) - for log in decoded_receipt.logs: - if log.address == DEPOSIT_CONTRACT_ADDRESS: - if ( - len(log.topics) > 0 - and log.topics[0] == DEPOSIT_EVENT_SIGNATURE_HASH - ): - request = extract_deposit_data(log.data) - deposit_requests += request + for receipt in receipts: + decoded_receipt = decode_receipt(receipt) + for log in decoded_receipt.logs: + if log.address == DEPOSIT_CONTRACT_ADDRESS: + if ( + len(log.topics) > 0 + and log.topics[0] == DEPOSIT_EVENT_SIGNATURE_HASH + ): + request = extract_deposit_data(log.data) + deposit_requests += request return deposit_requests diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index 3f0ac0f845..e587a83ebc 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -64,6 +64,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipts : + Receipts of all the transactions in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -82,12 +84,12 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipts: Tuple[Union[Bytes, Receipt], ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) blob_gas_used: U64 = U64(0) - deposit_requests: Bytes = Bytes(b"") requests: List[Bytes] = field(default_factory=list) From f78853dd58f2e49d58badd33b941fdf306e8b7db Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Tue, 22 Apr 2025 18:42:29 +0200 Subject: [PATCH 47/70] store receipt keys instead of receipts --- src/ethereum/prague/fork.py | 13 +++++------ src/ethereum/prague/requests.py | 16 +++++++------ src/ethereum/prague/vm/__init__.py | 6 ++--- .../evm_tools/loaders/fork_loader.py | 5 ++++ .../evm_tools/t8n/t8n_types.py | 23 +++++++++---------- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 2463b10565..d0f80b0e0d 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -35,7 +35,7 @@ DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, compute_requests_hash, - parse_deposit_requests_from_receipts, + parse_deposit_requests, ) from .state import ( State, @@ -655,9 +655,7 @@ def process_general_purpose_requests( The block output for the current block. """ # Requests are to be in ascending order of request type - deposit_requests = parse_deposit_requests_from_receipts( - block_output.receipts - ) + deposit_requests = parse_deposit_requests(block_output) requests_from_execution = block_output.requests if len(deposit_requests) > 0: requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) @@ -842,16 +840,17 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) block_output.block_logs += tx_output.logs - block_output.receipts += (receipt,) - def process_withdrawals( block_env: vm.BlockEnvironment, diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index 46581e0cb3..d7ce9a5e4a 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -9,7 +9,7 @@ """ from hashlib import sha256 -from typing import List, Tuple, Union +from typing import List from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen @@ -17,8 +17,10 @@ from ethereum.exceptions import InvalidBlock from ethereum.utils.hexadecimal import hex_to_bytes32 -from .blocks import Receipt, decode_receipt +from .blocks import decode_receipt +from .state import trie_get from .utils.hexadecimal import hex_to_address +from .vm import BlockOutput DEPOSIT_CONTRACT_ADDRESS = hex_to_address( "0x00000000219ab540356cbb839cbe05303d7705fa" @@ -146,14 +148,14 @@ def extract_deposit_data(data: Bytes) -> Bytes: return pubkey + withdrawal_credentials + amount + signature + index -def parse_deposit_requests_from_receipts( - receipts: Tuple[Union[Bytes, Receipt], ...], -) -> Bytes: +def parse_deposit_requests(block_output: BlockOutput) -> Bytes: """ - Parse deposit requests from a receipt. + Parse deposit requests from the block output. """ deposit_requests: Bytes = b"" - for receipt in receipts: + for key in block_output.receipt_keys: + receipt = trie_get(block_output.receipts_trie, key) + assert receipt is not None decoded_receipt = decode_receipt(receipt) for log in decoded_receipt.logs: if log.address == DEPOSIT_CONTRACT_ADDRESS: diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index e587a83ebc..dab7771379 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -64,8 +64,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. - receipts : - Receipts of all the transactions in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -84,7 +84,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) - receipts: Tuple[Union[Bytes, Receipt], ...] = field(default_factory=tuple) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 13904e90d5..f8bef3360c 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -267,6 +267,11 @@ def copy_trie(self) -> Any: """copy_trie function of the fork""" return self._module("trie").copy_trie + @property + def trie_get(self) -> Any: + """trie_get function of the fork""" + return self._module("trie").trie_get + @property def hex_to_address(self) -> Any: """hex_to_address function of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 505facd7d1..0e41e5fa32 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -269,22 +269,23 @@ class Result: requests: Optional[List[Bytes]] = None block_exception: Optional[str] = None - def get_receipts_from_tries( - self, t8n: Any, tx_trie: Any, receipts_trie: Any + def get_receipts_from_output( + self, + t8n: Any, + block_output: Any, ) -> List[Any]: """ Get receipts from the transaction and receipts tries. """ receipts: List[Any] = [] - for index in tx_trie._data: - if index not in receipts_trie._data: - # Meaning the transaction has somehow failed - return receipts + for key in block_output.receipt_keys: + tx = t8n.fork.trie_get(block_output.transactions_trie, key) + receipt = t8n.fork.trie_get(block_output.receipts_trie, key) - tx = tx_trie._data.get(index) - tx_hash = t8n.fork.get_transaction_hash(tx) + assert tx is not None + assert receipt is not None - receipt = receipts_trie._data.get(index) + tx_hash = t8n.fork.get_transaction_hash(tx) if hasattr(t8n.fork, "decode_receipt"): decoded_receipt = t8n.fork.decode_receipt(receipt) @@ -312,9 +313,7 @@ def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None: self.bloom = t8n.fork.logs_bloom(block_output.block_logs) self.logs_hash = keccak256(rlp.encode(block_output.block_logs)) self.state_root = t8n.fork.state_root(block_env.state) - self.receipts = self.get_receipts_from_tries( - t8n, block_output.transactions_trie, block_output.receipts_trie - ) + self.receipts = self.get_receipts_from_output(t8n, block_output) if hasattr(block_env, "base_fee_per_gas"): self.base_fee = block_env.base_fee_per_gas From e8b8988f15ea501b575eee2050099b696013c3af Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Wed, 23 Apr 2025 08:59:24 +0200 Subject: [PATCH 48/70] backport to older forks --- src/ethereum/arrow_glacier/fork.py | 5 ++++- src/ethereum/arrow_glacier/vm/__init__.py | 3 +++ src/ethereum/berlin/fork.py | 5 ++++- src/ethereum/berlin/vm/__init__.py | 3 +++ src/ethereum/byzantium/fork.py | 5 ++++- src/ethereum/byzantium/vm/__init__.py | 3 +++ src/ethereum/cancun/fork.py | 5 ++++- src/ethereum/cancun/vm/__init__.py | 3 +++ src/ethereum/constantinople/fork.py | 5 ++++- src/ethereum/constantinople/vm/__init__.py | 3 +++ src/ethereum/dao_fork/fork.py | 5 ++++- src/ethereum/dao_fork/vm/__init__.py | 3 +++ src/ethereum/frontier/fork.py | 5 ++++- src/ethereum/frontier/vm/__init__.py | 3 +++ src/ethereum/gray_glacier/fork.py | 5 ++++- src/ethereum/gray_glacier/vm/__init__.py | 3 +++ src/ethereum/homestead/fork.py | 5 ++++- src/ethereum/homestead/vm/__init__.py | 3 +++ src/ethereum/istanbul/fork.py | 5 ++++- src/ethereum/istanbul/vm/__init__.py | 3 +++ src/ethereum/london/fork.py | 5 ++++- src/ethereum/london/vm/__init__.py | 3 +++ src/ethereum/muir_glacier/fork.py | 5 ++++- src/ethereum/muir_glacier/vm/__init__.py | 3 +++ src/ethereum/paris/fork.py | 5 ++++- src/ethereum/paris/vm/__init__.py | 3 +++ src/ethereum/shanghai/fork.py | 5 ++++- src/ethereum/shanghai/vm/__init__.py | 3 +++ src/ethereum/spurious_dragon/fork.py | 5 ++++- src/ethereum/spurious_dragon/vm/__init__.py | 3 +++ src/ethereum/tangerine_whistle/fork.py | 5 ++++- src/ethereum/tangerine_whistle/vm/__init__.py | 3 +++ 32 files changed, 112 insertions(+), 16 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index b5bd754b15..aa05524561 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -793,9 +793,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/arrow_glacier/vm/__init__.py b/src/ethereum/arrow_glacier/vm/__init__.py index 245a05e454..ce60ea1105 100644 --- a/src/ethereum/arrow_glacier/vm/__init__.py +++ b/src/ethereum/arrow_glacier/vm/__init__.py @@ -62,6 +62,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -74,6 +76,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index f6c2f57442..48e011a8ef 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -692,9 +692,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/berlin/vm/__init__.py b/src/ethereum/berlin/vm/__init__.py index 76276e86fc..568cd21785 100644 --- a/src/ethereum/berlin/vm/__init__.py +++ b/src/ethereum/berlin/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index bd924561d1..9d0b6af9cf 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -669,9 +669,12 @@ def process_transaction( tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/byzantium/vm/__init__.py b/src/ethereum/byzantium/vm/__init__.py index 81df1e3ad4..89d41710a9 100644 --- a/src/ethereum/byzantium/vm/__init__.py +++ b/src/ethereum/byzantium/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index e11f0527e0..2dd3999fe0 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -731,9 +731,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index 811bbab24e..333135baf1 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -64,6 +64,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -80,6 +82,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 02d1e3f75b..4bd3861080 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -669,9 +669,12 @@ def process_transaction( tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/constantinople/vm/__init__.py b/src/ethereum/constantinople/vm/__init__.py index 81df1e3ad4..89d41710a9 100644 --- a/src/ethereum/constantinople/vm/__init__.py +++ b/src/ethereum/constantinople/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 20bc645ef9..0b47c2bc2c 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -673,9 +673,12 @@ def process_transaction( tx_output.logs, ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/dao_fork/vm/__init__.py b/src/ethereum/dao_fork/vm/__init__.py index d351223871..9485cdc84b 100644 --- a/src/ethereum/dao_fork/vm/__init__.py +++ b/src/ethereum/dao_fork/vm/__init__.py @@ -60,6 +60,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -72,6 +74,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 3127970d36..9288e73523 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -656,9 +656,12 @@ def process_transaction( tx_output.logs, ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/frontier/vm/__init__.py b/src/ethereum/frontier/vm/__init__.py index 5ea270ab77..ca107b25a5 100644 --- a/src/ethereum/frontier/vm/__init__.py +++ b/src/ethereum/frontier/vm/__init__.py @@ -60,6 +60,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -72,6 +74,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 92372ef4da..97491a3d9f 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -793,9 +793,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/gray_glacier/vm/__init__.py b/src/ethereum/gray_glacier/vm/__init__.py index 245a05e454..ce60ea1105 100644 --- a/src/ethereum/gray_glacier/vm/__init__.py +++ b/src/ethereum/gray_glacier/vm/__init__.py @@ -62,6 +62,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -74,6 +76,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index 813f5766f0..a46924c605 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -656,9 +656,12 @@ def process_transaction( tx_output.logs, ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/homestead/vm/__init__.py b/src/ethereum/homestead/vm/__init__.py index d351223871..9485cdc84b 100644 --- a/src/ethereum/homestead/vm/__init__.py +++ b/src/ethereum/homestead/vm/__init__.py @@ -60,6 +60,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -72,6 +74,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 02d1e3f75b..4bd3861080 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -669,9 +669,12 @@ def process_transaction( tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/istanbul/vm/__init__.py b/src/ethereum/istanbul/vm/__init__.py index 81df1e3ad4..89d41710a9 100644 --- a/src/ethereum/istanbul/vm/__init__.py +++ b/src/ethereum/istanbul/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index 4b05bea0f9..733e7b48e4 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -799,9 +799,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/london/vm/__init__.py b/src/ethereum/london/vm/__init__.py index 245a05e454..ce60ea1105 100644 --- a/src/ethereum/london/vm/__init__.py +++ b/src/ethereum/london/vm/__init__.py @@ -62,6 +62,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -74,6 +76,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index d8e65346dc..e49a26efd8 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -671,9 +671,12 @@ def process_transaction( tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/muir_glacier/vm/__init__.py b/src/ethereum/muir_glacier/vm/__init__.py index 81df1e3ad4..89d41710a9 100644 --- a/src/ethereum/muir_glacier/vm/__init__.py +++ b/src/ethereum/muir_glacier/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index 43c27acc71..ba4bd90c04 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -572,9 +572,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/paris/vm/__init__.py b/src/ethereum/paris/vm/__init__.py index fe4d472966..633ad70954 100644 --- a/src/ethereum/paris/vm/__init__.py +++ b/src/ethereum/paris/vm/__init__.py @@ -62,6 +62,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -74,6 +76,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 3ff1dd82a4..7632b6a433 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -585,9 +585,12 @@ def process_transaction( tx, tx_output.error, block_output.block_gas_used, tx_output.logs ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/shanghai/vm/__init__.py b/src/ethereum/shanghai/vm/__init__.py index 3d48ccaafe..ab9a558d98 100644 --- a/src/ethereum/shanghai/vm/__init__.py +++ b/src/ethereum/shanghai/vm/__init__.py @@ -62,6 +62,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -76,6 +78,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( default_factory=lambda: Trie(secured=False, default=None) diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index 77c4f74360..e4922b2552 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -664,9 +664,12 @@ def process_transaction( tx_output.logs, ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/spurious_dragon/vm/__init__.py b/src/ethereum/spurious_dragon/vm/__init__.py index b63a25edab..c892577a74 100644 --- a/src/ethereum/spurious_dragon/vm/__init__.py +++ b/src/ethereum/spurious_dragon/vm/__init__.py @@ -61,6 +61,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -73,6 +75,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index 9a13f28c3b..ebbb2456cb 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -655,9 +655,12 @@ def process_transaction( tx_output.logs, ) + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + trie_set( block_output.receipts_trie, - rlp.encode(Uint(index)), + receipt_key, receipt, ) diff --git a/src/ethereum/tangerine_whistle/vm/__init__.py b/src/ethereum/tangerine_whistle/vm/__init__.py index d351223871..9485cdc84b 100644 --- a/src/ethereum/tangerine_whistle/vm/__init__.py +++ b/src/ethereum/tangerine_whistle/vm/__init__.py @@ -60,6 +60,8 @@ class BlockOutput: Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. block_logs : `Bloom` Logs bloom of all the logs included in all the transactions of the block. @@ -72,6 +74,7 @@ class BlockOutput: receipts_trie: Trie[Bytes, Optional[Receipt]] = field( default_factory=lambda: Trie(secured=False, default=None) ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) block_logs: Tuple[Log, ...] = field(default_factory=tuple) From 8e0ed343dfe90c81d8c9ad66c9c391834b8152a0 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 22 Apr 2025 13:24:51 -0400 Subject: [PATCH 49/70] Add test for changing evm_trace (#994) --- tests/test_trace.py | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/test_trace.py diff --git a/tests/test_trace.py b/tests/test_trace.py new file mode 100644 index 0000000000..ae4c8fc611 --- /dev/null +++ b/tests/test_trace.py @@ -0,0 +1,73 @@ +from typing import Optional, cast + +from ethereum_types.numeric import Uint + +import ethereum.trace + + +def test_modify_evm_trace() -> None: + trace1: Optional[ethereum.trace.TraceEvent] = None + trace2: Optional[ethereum.trace.TraceEvent] = None + + def tracer1( + evm: object, + event: ethereum.trace.TraceEvent, + trace_memory: bool = False, + trace_stack: bool = True, + trace_return_data: bool = False, + ) -> None: + nonlocal trace1 + trace1 = event + + def tracer2( + evm: object, + event: ethereum.trace.TraceEvent, + trace_memory: bool = False, + trace_stack: bool = True, + trace_return_data: bool = False, + ) -> None: + nonlocal trace2 + trace2 = event + + ethereum.trace.evm_trace = tracer1 + + from ethereum.prague.vm import Evm, Message + from ethereum.prague.vm.gas import charge_gas + + evm = Evm( + pc=Uint(1), + stack=[], + memory=bytearray(), + code=b"", + gas_left=Uint(100), + valid_jump_destinations=set(), + logs=(), + refund_counter=0, + running=True, + message=cast(Message, object()), + output=b"", + accounts_to_delete=set(), + touched_accounts=set(), + return_data=b"", + error=None, + accessed_addresses=set(), + accessed_storage_keys=set(), + ) + + charge_gas(evm, Uint(5)) + + assert trace2 is None + assert isinstance(trace1, ethereum.trace.GasAndRefund) + assert trace1.gas_cost == 5 + + ethereum.trace.evm_trace = tracer2 + + charge_gas(evm, Uint(6)) + + # Check that the old event is unmodified. + assert isinstance(trace1, ethereum.trace.GasAndRefund) + assert trace1.gas_cost == 5 + + # Check that the new event is populated. + assert isinstance(trace2, ethereum.trace.GasAndRefund) + assert trace2.gas_cost == 6 From b1edd6b65f88ddf10bb39209ea204bfa46f4055e Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 22 Apr 2025 17:39:02 -0400 Subject: [PATCH 50/70] Fix changing evm_trace after import (closes #994) --- src/ethereum/trace.py | 37 ++++++++++++++++++- .../evm_tools/t8n/__init__.py | 14 ++++--- tests/conftest.py | 2 +- tests/test_trace.py | 4 +- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/ethereum/trace.py b/src/ethereum/trace.py index b3ac7f3981..9aaffcfd19 100644 --- a/src/ethereum/trace.py +++ b/src/ethereum/trace.py @@ -226,9 +226,44 @@ def __call__( """ -evm_trace: EvmTracer = discard_evm_trace +_evm_trace: EvmTracer = discard_evm_trace """ Active [`EvmTracer`] that is used for generating traces. [`EvmTracer`]: ref:ethereum.trace.EvmTracer """ + + +def set_evm_trace(tracer: EvmTracer) -> EvmTracer: + """ + Change the active [`EvmTracer`] that is used for generating traces. + + [`EvmTracer`]: ref:ethereum.trace.EvmTracer + """ + global _evm_trace + old = _evm_trace + _evm_trace = tracer + return old + + +def evm_trace( + evm: object, + event: TraceEvent, + /, + trace_memory: bool = False, + trace_stack: bool = True, + trace_return_data: bool = False, +) -> None: + """ + Emit a trace to the active [`EvmTracer`]. + + [`EvmTracer`]: ref:ethereum.trace.EvmTracer + """ + global _evm_trace + _evm_trace( + evm, + event, + trace_memory=trace_memory, + trace_stack=trace_stack, + trace_return_data=trace_return_data, + ) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 21bc7b18f6..50e02cb584 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -105,12 +105,14 @@ def __init__( trace_memory = getattr(self.options, "trace.memory", False) trace_stack = not getattr(self.options, "trace.nostack", False) trace_return_data = getattr(self.options, "trace.returndata") - trace.evm_trace = partial( - evm_trace, - trace_memory=trace_memory, - trace_stack=trace_stack, - trace_return_data=trace_return_data, - output_basedir=self.options.output_basedir, + trace.set_evm_trace( + partial( + evm_trace, + trace_memory=trace_memory, + trace_stack=trace_stack, + trace_return_data=trace_return_data, + output_basedir=self.options.output_basedir, + ) ) self.logger = get_stream_logger("T8N") diff --git a/tests/conftest.py b/tests/conftest.py index c1f7d03b2e..ab5eed5e7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,7 +53,7 @@ def pytest_configure(config: Config) -> None: ) # Replace the function in the module - ethereum.trace.evm_trace = new_trace_function + ethereum.trace.set_evm_trace(new_trace_function) def download_fixtures(url: str, location: str) -> None: diff --git a/tests/test_trace.py b/tests/test_trace.py index ae4c8fc611..11445d4b60 100644 --- a/tests/test_trace.py +++ b/tests/test_trace.py @@ -29,7 +29,7 @@ def tracer2( nonlocal trace2 trace2 = event - ethereum.trace.evm_trace = tracer1 + ethereum.trace.set_evm_trace(tracer1) from ethereum.prague.vm import Evm, Message from ethereum.prague.vm.gas import charge_gas @@ -60,7 +60,7 @@ def tracer2( assert isinstance(trace1, ethereum.trace.GasAndRefund) assert trace1.gas_cost == 5 - ethereum.trace.evm_trace = tracer2 + ethereum.trace.set_evm_trace(tracer2) charge_gas(evm, Uint(6)) From 001a057fcdecc9852809d4ea3499b28a6b9239da Mon Sep 17 00:00:00 2001 From: DistributedDoge <49067954+DistributedDoge@users.noreply.github.com> Date: Sun, 3 Mar 2024 04:32:26 +0100 Subject: [PATCH 51/70] Use type alias instead of raw type --- src/ethereum/arrow_glacier/fork_types.py | 2 +- src/ethereum/berlin/fork_types.py | 2 +- src/ethereum/byzantium/fork_types.py | 2 +- src/ethereum/cancun/fork_types.py | 2 +- src/ethereum/constantinople/fork_types.py | 2 +- src/ethereum/dao_fork/fork_types.py | 2 +- src/ethereum/frontier/fork_types.py | 2 +- src/ethereum/gray_glacier/fork_types.py | 2 +- src/ethereum/homestead/fork_types.py | 2 +- src/ethereum/istanbul/fork_types.py | 2 +- src/ethereum/london/fork_types.py | 2 +- src/ethereum/muir_glacier/fork_types.py | 2 +- src/ethereum/paris/fork_types.py | 2 +- src/ethereum/prague/fork_types.py | 2 +- src/ethereum/shanghai/fork_types.py | 2 +- src/ethereum/spurious_dragon/fork_types.py | 2 +- src/ethereum/tangerine_whistle/fork_types.py | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork_types.py b/src/ethereum/arrow_glacier/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/arrow_glacier/fork_types.py +++ b/src/ethereum/arrow_glacier/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/berlin/fork_types.py b/src/ethereum/berlin/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/berlin/fork_types.py +++ b/src/ethereum/berlin/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/byzantium/fork_types.py b/src/ethereum/byzantium/fork_types.py index 328a2779ee..e3d593c6bf 100644 --- a/src/ethereum/byzantium/fork_types.py +++ b/src/ethereum/byzantium/fork_types.py @@ -35,7 +35,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/cancun/fork_types.py b/src/ethereum/cancun/fork_types.py index dc287c84f1..52fa4b247b 100644 --- a/src/ethereum/cancun/fork_types.py +++ b/src/ethereum/cancun/fork_types.py @@ -37,7 +37,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/constantinople/fork_types.py b/src/ethereum/constantinople/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/constantinople/fork_types.py +++ b/src/ethereum/constantinople/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/dao_fork/fork_types.py b/src/ethereum/dao_fork/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/dao_fork/fork_types.py +++ b/src/ethereum/dao_fork/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/frontier/fork_types.py b/src/ethereum/frontier/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/frontier/fork_types.py +++ b/src/ethereum/frontier/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/gray_glacier/fork_types.py b/src/ethereum/gray_glacier/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/gray_glacier/fork_types.py +++ b/src/ethereum/gray_glacier/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/homestead/fork_types.py b/src/ethereum/homestead/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/homestead/fork_types.py +++ b/src/ethereum/homestead/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/istanbul/fork_types.py b/src/ethereum/istanbul/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/istanbul/fork_types.py +++ b/src/ethereum/istanbul/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/london/fork_types.py b/src/ethereum/london/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/london/fork_types.py +++ b/src/ethereum/london/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/muir_glacier/fork_types.py b/src/ethereum/muir_glacier/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/muir_glacier/fork_types.py +++ b/src/ethereum/muir_glacier/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/paris/fork_types.py b/src/ethereum/paris/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/paris/fork_types.py +++ b/src/ethereum/paris/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/prague/fork_types.py b/src/ethereum/prague/fork_types.py index 26b6656190..74cf6c83cb 100644 --- a/src/ethereum/prague/fork_types.py +++ b/src/ethereum/prague/fork_types.py @@ -37,7 +37,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/shanghai/fork_types.py b/src/ethereum/shanghai/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/shanghai/fork_types.py +++ b/src/ethereum/shanghai/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/spurious_dragon/fork_types.py b/src/ethereum/spurious_dragon/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/spurious_dragon/fork_types.py +++ b/src/ethereum/spurious_dragon/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( diff --git a/src/ethereum/tangerine_whistle/fork_types.py b/src/ethereum/tangerine_whistle/fork_types.py index 00817ba506..c45044c60b 100644 --- a/src/ethereum/tangerine_whistle/fork_types.py +++ b/src/ethereum/tangerine_whistle/fork_types.py @@ -36,7 +36,7 @@ class Account: nonce: Uint balance: U256 - code: bytes + code: Bytes EMPTY_ACCOUNT = Account( From d54528ea0712d6462dbafb9d399828dadea94447 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Fri, 25 Apr 2025 14:42:55 -0400 Subject: [PATCH 52/70] More `bytes` -> `Bytes` changes --- src/ethereum/arrow_glacier/blocks.py | 2 +- src/ethereum/arrow_glacier/bloom.py | 3 ++- src/ethereum/arrow_glacier/utils/hexadecimal.py | 8 +++++--- .../arrow_glacier/vm/instructions/arithmetic.py | 3 ++- src/ethereum/arrow_glacier/vm/instructions/system.py | 4 ++-- src/ethereum/arrow_glacier/vm/runtime.py | 3 ++- src/ethereum/berlin/blocks.py | 2 +- src/ethereum/berlin/bloom.py | 3 ++- src/ethereum/berlin/utils/hexadecimal.py | 8 +++++--- src/ethereum/berlin/vm/instructions/arithmetic.py | 3 ++- src/ethereum/berlin/vm/instructions/system.py | 4 ++-- src/ethereum/berlin/vm/runtime.py | 3 ++- src/ethereum/byzantium/blocks.py | 2 +- src/ethereum/byzantium/bloom.py | 3 ++- src/ethereum/byzantium/utils/hexadecimal.py | 8 +++++--- src/ethereum/byzantium/vm/instructions/arithmetic.py | 3 ++- src/ethereum/byzantium/vm/instructions/system.py | 4 ++-- src/ethereum/byzantium/vm/runtime.py | 3 ++- src/ethereum/cancun/blocks.py | 2 +- src/ethereum/cancun/bloom.py | 3 ++- src/ethereum/cancun/utils/hexadecimal.py | 8 +++++--- src/ethereum/cancun/vm/instructions/arithmetic.py | 3 ++- src/ethereum/cancun/vm/instructions/system.py | 4 ++-- src/ethereum/cancun/vm/runtime.py | 3 ++- src/ethereum/constantinople/blocks.py | 2 +- src/ethereum/constantinople/bloom.py | 3 ++- src/ethereum/constantinople/utils/hexadecimal.py | 8 +++++--- .../constantinople/vm/instructions/arithmetic.py | 3 ++- .../constantinople/vm/instructions/system.py | 4 ++-- src/ethereum/constantinople/vm/runtime.py | 3 ++- src/ethereum/dao_fork/blocks.py | 2 +- src/ethereum/dao_fork/bloom.py | 3 ++- src/ethereum/dao_fork/utils/hexadecimal.py | 8 +++++--- src/ethereum/dao_fork/vm/instructions/arithmetic.py | 3 ++- src/ethereum/dao_fork/vm/runtime.py | 3 ++- src/ethereum/ethash.py | 10 +++++----- src/ethereum/frontier/blocks.py | 2 +- src/ethereum/frontier/bloom.py | 3 ++- src/ethereum/frontier/utils/hexadecimal.py | 8 +++++--- src/ethereum/frontier/vm/instructions/arithmetic.py | 3 ++- src/ethereum/frontier/vm/runtime.py | 3 ++- src/ethereum/genesis.py | 2 +- src/ethereum/gray_glacier/blocks.py | 2 +- src/ethereum/gray_glacier/bloom.py | 3 ++- src/ethereum/gray_glacier/utils/hexadecimal.py | 8 +++++--- .../gray_glacier/vm/instructions/arithmetic.py | 3 ++- src/ethereum/gray_glacier/vm/instructions/system.py | 4 ++-- src/ethereum/gray_glacier/vm/runtime.py | 3 ++- src/ethereum/homestead/blocks.py | 2 +- src/ethereum/homestead/bloom.py | 3 ++- src/ethereum/homestead/utils/hexadecimal.py | 8 +++++--- src/ethereum/homestead/vm/instructions/arithmetic.py | 3 ++- src/ethereum/homestead/vm/runtime.py | 3 ++- src/ethereum/istanbul/blocks.py | 2 +- src/ethereum/istanbul/bloom.py | 3 ++- src/ethereum/istanbul/utils/hexadecimal.py | 8 +++++--- src/ethereum/istanbul/vm/instructions/arithmetic.py | 3 ++- src/ethereum/istanbul/vm/instructions/system.py | 4 ++-- src/ethereum/istanbul/vm/runtime.py | 3 ++- src/ethereum/london/blocks.py | 2 +- src/ethereum/london/bloom.py | 3 ++- src/ethereum/london/utils/hexadecimal.py | 8 +++++--- src/ethereum/london/vm/instructions/arithmetic.py | 3 ++- src/ethereum/london/vm/instructions/system.py | 4 ++-- src/ethereum/london/vm/runtime.py | 3 ++- src/ethereum/muir_glacier/blocks.py | 2 +- src/ethereum/muir_glacier/bloom.py | 3 ++- src/ethereum/muir_glacier/utils/hexadecimal.py | 8 +++++--- .../muir_glacier/vm/instructions/arithmetic.py | 3 ++- src/ethereum/muir_glacier/vm/instructions/system.py | 4 ++-- src/ethereum/muir_glacier/vm/runtime.py | 3 ++- src/ethereum/paris/blocks.py | 2 +- src/ethereum/paris/bloom.py | 3 ++- src/ethereum/paris/utils/hexadecimal.py | 8 +++++--- src/ethereum/paris/vm/instructions/arithmetic.py | 3 ++- src/ethereum/paris/vm/instructions/system.py | 4 ++-- src/ethereum/paris/vm/runtime.py | 3 ++- src/ethereum/prague/blocks.py | 2 +- src/ethereum/prague/bloom.py | 3 ++- src/ethereum/prague/utils/hexadecimal.py | 8 +++++--- src/ethereum/prague/vm/instructions/arithmetic.py | 3 ++- src/ethereum/prague/vm/instructions/system.py | 6 +++--- src/ethereum/prague/vm/runtime.py | 3 ++- src/ethereum/shanghai/blocks.py | 2 +- src/ethereum/shanghai/bloom.py | 3 ++- src/ethereum/shanghai/utils/hexadecimal.py | 8 +++++--- src/ethereum/shanghai/vm/instructions/arithmetic.py | 3 ++- src/ethereum/shanghai/vm/instructions/system.py | 4 ++-- src/ethereum/shanghai/vm/runtime.py | 3 ++- src/ethereum/spurious_dragon/blocks.py | 2 +- src/ethereum/spurious_dragon/bloom.py | 3 ++- src/ethereum/spurious_dragon/utils/hexadecimal.py | 8 +++++--- .../spurious_dragon/vm/instructions/arithmetic.py | 3 ++- src/ethereum/spurious_dragon/vm/runtime.py | 3 ++- src/ethereum/tangerine_whistle/blocks.py | 2 +- src/ethereum/tangerine_whistle/bloom.py | 3 ++- src/ethereum/tangerine_whistle/utils/hexadecimal.py | 8 +++++--- .../tangerine_whistle/vm/instructions/arithmetic.py | 3 ++- src/ethereum/tangerine_whistle/vm/runtime.py | 3 ++- src/ethereum/trace.py | 6 ++++-- src/ethereum/utils/hexadecimal.py | 12 ++++++------ 101 files changed, 245 insertions(+), 158 deletions(-) diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index 38002554ca..2d75a2aa1b 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -72,7 +72,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/arrow_glacier/bloom.py b/src/ethereum/arrow_glacier/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/arrow_glacier/bloom.py +++ b/src/ethereum/arrow_glacier/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/arrow_glacier/utils/hexadecimal.py b/src/ethereum/arrow_glacier/utils/hexadecimal.py index 0666f1c7ca..f89513dc57 100644 --- a/src/ethereum/arrow_glacier/utils/hexadecimal.py +++ b/src/ethereum/arrow_glacier/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Arrow Glacier types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/arrow_glacier/vm/instructions/arithmetic.py b/src/ethereum/arrow_glacier/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/arithmetic.py +++ b/src/ethereum/arrow_glacier/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/arrow_glacier/vm/instructions/system.py b/src/ethereum/arrow_glacier/vm/instructions/system.py index c08b42ccb8..22722995a7 100644 --- a/src/ethereum/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/arrow_glacier/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -680,7 +680,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/arrow_glacier/vm/runtime.py b/src/ethereum/arrow_glacier/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/arrow_glacier/vm/runtime.py +++ b/src/ethereum/arrow_glacier/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index ce975fa4db..17b3ebe909 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -66,7 +66,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/berlin/bloom.py b/src/ethereum/berlin/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/berlin/bloom.py +++ b/src/ethereum/berlin/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/berlin/utils/hexadecimal.py b/src/ethereum/berlin/utils/hexadecimal.py index 7ddf16fcd0..3cfc657e6e 100644 --- a/src/ethereum/berlin/utils/hexadecimal.py +++ b/src/ethereum/berlin/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Berlin types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/berlin/vm/instructions/arithmetic.py b/src/ethereum/berlin/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/berlin/vm/instructions/arithmetic.py +++ b/src/ethereum/berlin/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/berlin/vm/instructions/system.py b/src/ethereum/berlin/vm/instructions/system.py index 57ec1826dd..59ab089d69 100644 --- a/src/ethereum/berlin/vm/instructions/system.py +++ b/src/ethereum/berlin/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -692,7 +692,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/berlin/vm/runtime.py b/src/ethereum/berlin/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/berlin/vm/runtime.py +++ b/src/ethereum/berlin/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/byzantium/blocks.py b/src/ethereum/byzantium/blocks.py index f74ead6616..4be3d56b68 100644 --- a/src/ethereum/byzantium/blocks.py +++ b/src/ethereum/byzantium/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/byzantium/bloom.py b/src/ethereum/byzantium/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/byzantium/bloom.py +++ b/src/ethereum/byzantium/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/byzantium/utils/hexadecimal.py b/src/ethereum/byzantium/utils/hexadecimal.py index 9af5876283..8ae1d5a5aa 100644 --- a/src/ethereum/byzantium/utils/hexadecimal.py +++ b/src/ethereum/byzantium/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Byzantium types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/byzantium/vm/instructions/arithmetic.py b/src/ethereum/byzantium/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/byzantium/vm/instructions/arithmetic.py +++ b/src/ethereum/byzantium/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/byzantium/vm/instructions/system.py b/src/ethereum/byzantium/vm/instructions/system.py index 827346bc97..9e543dae2c 100644 --- a/src/ethereum/byzantium/vm/instructions/system.py +++ b/src/ethereum/byzantium/vm/instructions/system.py @@ -11,7 +11,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ...fork_types import Address @@ -587,7 +587,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/byzantium/vm/runtime.py b/src/ethereum/byzantium/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/byzantium/vm/runtime.py +++ b/src/ethereum/byzantium/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index f1ac3fd758..1c600aae0d 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -91,7 +91,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/cancun/bloom.py b/src/ethereum/cancun/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/cancun/bloom.py +++ b/src/ethereum/cancun/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/cancun/utils/hexadecimal.py b/src/ethereum/cancun/utils/hexadecimal.py index cbf892c73e..4170fc9e4c 100644 --- a/src/ethereum/cancun/utils/hexadecimal.py +++ b/src/ethereum/cancun/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Cancun types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/cancun/vm/instructions/arithmetic.py b/src/ethereum/cancun/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/cancun/vm/instructions/arithmetic.py +++ b/src/ethereum/cancun/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index 9e503d4a72..e26b0c2d48 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -702,7 +702,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/cancun/vm/runtime.py b/src/ethereum/cancun/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/cancun/vm/runtime.py +++ b/src/ethereum/cancun/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/constantinople/blocks.py b/src/ethereum/constantinople/blocks.py index f74ead6616..4be3d56b68 100644 --- a/src/ethereum/constantinople/blocks.py +++ b/src/ethereum/constantinople/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/constantinople/bloom.py b/src/ethereum/constantinople/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/constantinople/bloom.py +++ b/src/ethereum/constantinople/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/constantinople/utils/hexadecimal.py b/src/ethereum/constantinople/utils/hexadecimal.py index 8281d94092..eb78b1590d 100644 --- a/src/ethereum/constantinople/utils/hexadecimal.py +++ b/src/ethereum/constantinople/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Constantinople types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/constantinople/vm/instructions/arithmetic.py b/src/ethereum/constantinople/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/constantinople/vm/instructions/arithmetic.py +++ b/src/ethereum/constantinople/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/constantinople/vm/instructions/system.py b/src/ethereum/constantinople/vm/instructions/system.py index eaa8a98601..332da1ab10 100644 --- a/src/ethereum/constantinople/vm/instructions/system.py +++ b/src/ethereum/constantinople/vm/instructions/system.py @@ -11,7 +11,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -658,7 +658,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/constantinople/vm/runtime.py b/src/ethereum/constantinople/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/constantinople/vm/runtime.py +++ b/src/ethereum/constantinople/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/dao_fork/blocks.py b/src/ethereum/dao_fork/blocks.py index 713b5aecf5..03bde3680e 100644 --- a/src/ethereum/dao_fork/blocks.py +++ b/src/ethereum/dao_fork/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/dao_fork/bloom.py b/src/ethereum/dao_fork/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/dao_fork/bloom.py +++ b/src/ethereum/dao_fork/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/dao_fork/utils/hexadecimal.py b/src/ethereum/dao_fork/utils/hexadecimal.py index cc1e3ef9ff..d8ec83683f 100644 --- a/src/ethereum/dao_fork/utils/hexadecimal.py +++ b/src/ethereum/dao_fork/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Dao Fork types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/dao_fork/vm/instructions/arithmetic.py b/src/ethereum/dao_fork/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/dao_fork/vm/instructions/arithmetic.py +++ b/src/ethereum/dao_fork/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/dao_fork/vm/runtime.py b/src/ethereum/dao_fork/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/dao_fork/vm/runtime.py +++ b/src/ethereum/dao_fork/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/ethash.py b/src/ethereum/ethash.py index 6cb9aca590..490d729d20 100644 --- a/src/ethereum/ethash.py +++ b/src/ethereum/ethash.py @@ -28,7 +28,7 @@ from typing import Callable, Tuple, Union -from ethereum_types.bytes import Bytes8 +from ethereum_types.bytes import Bytes, Bytes8 from ethereum_types.numeric import U32, Uint, ulen from ethereum.crypto.hash import Hash32, Hash64, keccak256, keccak512 @@ -239,7 +239,7 @@ def generate_cache(block_number: Uint) -> Tuple[Tuple[U32, ...], ...]: second_cache_item = cache[ U32.from_le_bytes(cache[index][0:4]) % U32(cache_size_words) ] - result = bytes( + result = Bytes( [a ^ b for a, b in zip(first_cache_item, second_cache_item)] ) cache[index] = keccak512(result) @@ -342,7 +342,7 @@ def hashimoto( nonce: Bytes8, dataset_size: Uint, fetch_dataset_item: Callable[[Uint], Tuple[U32, ...]], -) -> Tuple[bytes, Hash32]: +) -> Tuple[Bytes, Hash32]: """ Obtain the mix digest and the final value for a header, by aggregating data from the full dataset. @@ -363,7 +363,7 @@ def hashimoto( [`dataset_size`]: ref:ethereum.ethash.dataset_size """ - nonce_le = bytes(reversed(nonce)) + nonce_le = Bytes(reversed(nonce)) seed_hash = keccak512(header_hash + nonce_le) seed_head = U32.from_le_bytes(seed_hash[:4]) @@ -397,7 +397,7 @@ def hashimoto_light( nonce: Bytes8, cache: Tuple[Tuple[U32, ...], ...], dataset_size: Uint, -) -> Tuple[bytes, Hash32]: +) -> Tuple[Bytes, Hash32]: """ Run the [`hashimoto`] algorithm by generating dataset item using the cache instead of loading the full dataset into main memory. diff --git a/src/ethereum/frontier/blocks.py b/src/ethereum/frontier/blocks.py index 713b5aecf5..03bde3680e 100644 --- a/src/ethereum/frontier/blocks.py +++ b/src/ethereum/frontier/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/frontier/bloom.py b/src/ethereum/frontier/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/frontier/bloom.py +++ b/src/ethereum/frontier/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/frontier/utils/hexadecimal.py b/src/ethereum/frontier/utils/hexadecimal.py index b1789e19e6..a54b908eb7 100644 --- a/src/ethereum/frontier/utils/hexadecimal.py +++ b/src/ethereum/frontier/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Frontier types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/frontier/vm/instructions/arithmetic.py b/src/ethereum/frontier/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/frontier/vm/instructions/arithmetic.py +++ b/src/ethereum/frontier/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/frontier/vm/runtime.py b/src/ethereum/frontier/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/frontier/vm/runtime.py +++ b/src/ethereum/frontier/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/genesis.py b/src/ethereum/genesis.py index 152a37c58b..564d639994 100644 --- a/src/ethereum/genesis.py +++ b/src/ethereum/genesis.py @@ -143,7 +143,7 @@ class GenesisFork( """ Address: Type[FixedBytes] - Account: Callable[[Uint, U256, bytes], AccountT] + Account: Callable[[Uint, U256, Bytes], AccountT] Trie: Callable[[bool, object], TrieT] Bloom: Type[FixedBytes] Header: Type[HeaderT] diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index 38002554ca..2d75a2aa1b 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -72,7 +72,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/gray_glacier/bloom.py b/src/ethereum/gray_glacier/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/gray_glacier/bloom.py +++ b/src/ethereum/gray_glacier/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/gray_glacier/utils/hexadecimal.py b/src/ethereum/gray_glacier/utils/hexadecimal.py index 0d5977c1f4..05b29fd0d8 100644 --- a/src/ethereum/gray_glacier/utils/hexadecimal.py +++ b/src/ethereum/gray_glacier/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Gray Glacier types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/gray_glacier/vm/instructions/arithmetic.py b/src/ethereum/gray_glacier/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/gray_glacier/vm/instructions/arithmetic.py +++ b/src/ethereum/gray_glacier/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/gray_glacier/vm/instructions/system.py b/src/ethereum/gray_glacier/vm/instructions/system.py index c08b42ccb8..22722995a7 100644 --- a/src/ethereum/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/gray_glacier/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -680,7 +680,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/gray_glacier/vm/runtime.py b/src/ethereum/gray_glacier/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/gray_glacier/vm/runtime.py +++ b/src/ethereum/gray_glacier/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/homestead/blocks.py b/src/ethereum/homestead/blocks.py index 713b5aecf5..03bde3680e 100644 --- a/src/ethereum/homestead/blocks.py +++ b/src/ethereum/homestead/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/homestead/bloom.py b/src/ethereum/homestead/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/homestead/bloom.py +++ b/src/ethereum/homestead/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/homestead/utils/hexadecimal.py b/src/ethereum/homestead/utils/hexadecimal.py index df2ac49721..a873cc85e6 100644 --- a/src/ethereum/homestead/utils/hexadecimal.py +++ b/src/ethereum/homestead/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Homestead types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/homestead/vm/instructions/arithmetic.py b/src/ethereum/homestead/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/homestead/vm/instructions/arithmetic.py +++ b/src/ethereum/homestead/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/homestead/vm/runtime.py b/src/ethereum/homestead/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/homestead/vm/runtime.py +++ b/src/ethereum/homestead/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/istanbul/blocks.py b/src/ethereum/istanbul/blocks.py index f74ead6616..4be3d56b68 100644 --- a/src/ethereum/istanbul/blocks.py +++ b/src/ethereum/istanbul/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/istanbul/bloom.py b/src/ethereum/istanbul/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/istanbul/bloom.py +++ b/src/ethereum/istanbul/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/istanbul/utils/hexadecimal.py b/src/ethereum/istanbul/utils/hexadecimal.py index d9bff56258..9ad0cec48b 100644 --- a/src/ethereum/istanbul/utils/hexadecimal.py +++ b/src/ethereum/istanbul/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Istanbul types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/istanbul/vm/instructions/arithmetic.py b/src/ethereum/istanbul/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/istanbul/vm/instructions/arithmetic.py +++ b/src/ethereum/istanbul/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/istanbul/vm/instructions/system.py b/src/ethereum/istanbul/vm/instructions/system.py index eaa8a98601..332da1ab10 100644 --- a/src/ethereum/istanbul/vm/instructions/system.py +++ b/src/ethereum/istanbul/vm/instructions/system.py @@ -11,7 +11,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -658,7 +658,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/istanbul/vm/runtime.py b/src/ethereum/istanbul/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/istanbul/vm/runtime.py +++ b/src/ethereum/istanbul/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 38002554ca..2d75a2aa1b 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -72,7 +72,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/london/bloom.py b/src/ethereum/london/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/london/bloom.py +++ b/src/ethereum/london/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/london/utils/hexadecimal.py b/src/ethereum/london/utils/hexadecimal.py index 09f516fed3..40cf31c42b 100644 --- a/src/ethereum/london/utils/hexadecimal.py +++ b/src/ethereum/london/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to London types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/london/vm/instructions/arithmetic.py b/src/ethereum/london/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/london/vm/instructions/arithmetic.py +++ b/src/ethereum/london/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/london/vm/instructions/system.py b/src/ethereum/london/vm/instructions/system.py index c08b42ccb8..22722995a7 100644 --- a/src/ethereum/london/vm/instructions/system.py +++ b/src/ethereum/london/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -680,7 +680,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/london/vm/runtime.py b/src/ethereum/london/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/london/vm/runtime.py +++ b/src/ethereum/london/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/muir_glacier/blocks.py b/src/ethereum/muir_glacier/blocks.py index f74ead6616..4be3d56b68 100644 --- a/src/ethereum/muir_glacier/blocks.py +++ b/src/ethereum/muir_glacier/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/muir_glacier/bloom.py b/src/ethereum/muir_glacier/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/muir_glacier/bloom.py +++ b/src/ethereum/muir_glacier/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/muir_glacier/utils/hexadecimal.py b/src/ethereum/muir_glacier/utils/hexadecimal.py index 30b29da816..167275f4e0 100644 --- a/src/ethereum/muir_glacier/utils/hexadecimal.py +++ b/src/ethereum/muir_glacier/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Muir Glacier types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/muir_glacier/vm/instructions/arithmetic.py b/src/ethereum/muir_glacier/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/muir_glacier/vm/instructions/arithmetic.py +++ b/src/ethereum/muir_glacier/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/muir_glacier/vm/instructions/system.py b/src/ethereum/muir_glacier/vm/instructions/system.py index eaa8a98601..332da1ab10 100644 --- a/src/ethereum/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/muir_glacier/vm/instructions/system.py @@ -11,7 +11,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -658,7 +658,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/muir_glacier/vm/runtime.py b/src/ethereum/muir_glacier/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/muir_glacier/vm/runtime.py +++ b/src/ethereum/muir_glacier/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index 7d2f79dd72..6ad5119f5f 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -72,7 +72,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/paris/bloom.py b/src/ethereum/paris/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/paris/bloom.py +++ b/src/ethereum/paris/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/paris/utils/hexadecimal.py b/src/ethereum/paris/utils/hexadecimal.py index 0cbd9d05a4..4eadb75b47 100644 --- a/src/ethereum/paris/utils/hexadecimal.py +++ b/src/ethereum/paris/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Paris types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/paris/vm/instructions/arithmetic.py b/src/ethereum/paris/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/paris/vm/instructions/arithmetic.py +++ b/src/ethereum/paris/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index c08b42ccb8..22722995a7 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -680,7 +680,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/paris/vm/runtime.py b/src/ethereum/paris/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/paris/vm/runtime.py +++ b/src/ethereum/paris/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index 15208d4ad0..80b29087e9 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -93,7 +93,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/prague/bloom.py b/src/ethereum/prague/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/prague/bloom.py +++ b/src/ethereum/prague/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/prague/utils/hexadecimal.py b/src/ethereum/prague/utils/hexadecimal.py index 5d6090084c..ea58c3ef3c 100644 --- a/src/ethereum/prague/utils/hexadecimal.py +++ b/src/ethereum/prague/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Prague types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/prague/vm/instructions/arithmetic.py b/src/ethereum/prague/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/prague/vm/instructions/arithmetic.py +++ b/src/ethereum/prague/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index fadb0da6ad..4654774d5d 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -290,7 +290,7 @@ def generic_call( memory_input_size: U256, memory_output_start_position: U256, memory_output_size: U256, - code: bytes, + code: Bytes, disable_precompiles: bool, ) -> None: """ @@ -745,7 +745,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/prague/vm/runtime.py b/src/ethereum/prague/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/prague/vm/runtime.py +++ b/src/ethereum/prague/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index 9a3f6a5927..b37e2fd921 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -87,7 +87,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/shanghai/bloom.py b/src/ethereum/shanghai/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/shanghai/bloom.py +++ b/src/ethereum/shanghai/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/shanghai/utils/hexadecimal.py b/src/ethereum/shanghai/utils/hexadecimal.py index 790f675919..47f6575f8a 100644 --- a/src/ethereum/shanghai/utils/hexadecimal.py +++ b/src/ethereum/shanghai/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Shanghai types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/shanghai/vm/instructions/arithmetic.py b/src/ethereum/shanghai/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/shanghai/vm/instructions/arithmetic.py +++ b/src/ethereum/shanghai/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 86001b1646..7682eb38b5 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -12,7 +12,7 @@ Implementations of the EVM system related instructions. """ -from ethereum_types.bytes import Bytes0 +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import ceil32 @@ -703,7 +703,7 @@ def revert(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) + evm.output = Bytes(output) raise Revert # PROGRAM COUNTER diff --git a/src/ethereum/shanghai/vm/runtime.py b/src/ethereum/shanghai/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/shanghai/vm/runtime.py +++ b/src/ethereum/shanghai/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/spurious_dragon/blocks.py b/src/ethereum/spurious_dragon/blocks.py index 713b5aecf5..03bde3680e 100644 --- a/src/ethereum/spurious_dragon/blocks.py +++ b/src/ethereum/spurious_dragon/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/spurious_dragon/bloom.py b/src/ethereum/spurious_dragon/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/spurious_dragon/bloom.py +++ b/src/ethereum/spurious_dragon/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/spurious_dragon/utils/hexadecimal.py b/src/ethereum/spurious_dragon/utils/hexadecimal.py index 9729946c45..5ba33f706a 100644 --- a/src/ethereum/spurious_dragon/utils/hexadecimal.py +++ b/src/ethereum/spurious_dragon/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Spurious Dragon types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/spurious_dragon/vm/instructions/arithmetic.py b/src/ethereum/spurious_dragon/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/spurious_dragon/vm/instructions/arithmetic.py +++ b/src/ethereum/spurious_dragon/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/spurious_dragon/vm/runtime.py b/src/ethereum/spurious_dragon/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/spurious_dragon/vm/runtime.py +++ b/src/ethereum/spurious_dragon/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/tangerine_whistle/blocks.py b/src/ethereum/tangerine_whistle/blocks.py index 713b5aecf5..03bde3680e 100644 --- a/src/ethereum/tangerine_whistle/blocks.py +++ b/src/ethereum/tangerine_whistle/blocks.py @@ -65,7 +65,7 @@ class Log: address: Address topics: Tuple[Hash32, ...] - data: bytes + data: Bytes @slotted_freezable diff --git a/src/ethereum/tangerine_whistle/bloom.py b/src/ethereum/tangerine_whistle/bloom.py index 0ba6e431ab..63db26dab5 100644 --- a/src/ethereum/tangerine_whistle/bloom.py +++ b/src/ethereum/tangerine_whistle/bloom.py @@ -18,6 +18,7 @@ from typing import Tuple +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint from ethereum.crypto.hash import keccak256 @@ -26,7 +27,7 @@ from .fork_types import Bloom -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: """ Add a bloom entry to the bloom filter (`bloom`). diff --git a/src/ethereum/tangerine_whistle/utils/hexadecimal.py b/src/ethereum/tangerine_whistle/utils/hexadecimal.py index f7d7c096da..e37e6c5113 100644 --- a/src/ethereum/tangerine_whistle/utils/hexadecimal.py +++ b/src/ethereum/tangerine_whistle/utils/hexadecimal.py @@ -12,6 +12,8 @@ Hexadecimal utility functions used in this specification, specific to Tangerine Whistle types. """ +from ethereum_types.bytes import Bytes + from ethereum.utils.hexadecimal import remove_hex_prefix from ..fork_types import Address, Bloom, Root @@ -31,7 +33,7 @@ def hex_to_root(hex_string: str) -> Root: root : `Root` Trie root obtained from the given hexadecimal string. """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_bloom(hex_string: str) -> Bloom: @@ -48,7 +50,7 @@ def hex_to_bloom(hex_string: str) -> Bloom: bloom : `Bloom` Bloom obtained from the given hexadecimal string. """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_address(hex_string: str) -> Address: @@ -65,4 +67,4 @@ def hex_to_address(hex_string: str) -> Address: address : `Address` The address obtained from the given hexadecimal string. """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/tangerine_whistle/vm/instructions/arithmetic.py b/src/ethereum/tangerine_whistle/vm/instructions/arithmetic.py index 0b8df99543..28c97db189 100644 --- a/src/ethereum/tangerine_whistle/vm/instructions/arithmetic.py +++ b/src/ethereum/tangerine_whistle/vm/instructions/arithmetic.py @@ -12,6 +12,7 @@ Implementations of the EVM Arithmetic instructions. """ +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.utils.numeric import get_sign @@ -354,7 +355,7 @@ def signextend(evm: Evm) -> None: result = value else: # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) + value_bytes = Bytes(value.to_be_bytes32()) # Now among the obtained value bytes, consider only # N `least significant bytes`, where N is `byte_num + 1`. value_bytes = value_bytes[31 - int(byte_num) :] diff --git a/src/ethereum/tangerine_whistle/vm/runtime.py b/src/ethereum/tangerine_whistle/vm/runtime.py index d6bf90a812..acead2be90 100644 --- a/src/ethereum/tangerine_whistle/vm/runtime.py +++ b/src/ethereum/tangerine_whistle/vm/runtime.py @@ -13,12 +13,13 @@ """ from typing import Set +from ethereum_types.bytes import Bytes from ethereum_types.numeric import Uint, ulen from .instructions import Ops -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: """ Analyze the evm code to obtain the set of valid jump destinations. diff --git a/src/ethereum/trace.py b/src/ethereum/trace.py index 9aaffcfd19..223f96e5b7 100644 --- a/src/ethereum/trace.py +++ b/src/ethereum/trace.py @@ -20,6 +20,8 @@ from dataclasses import dataclass from typing import Optional, Protocol, Union +from ethereum_types.bytes import Bytes + from ethereum.exceptions import EthereumException @@ -41,7 +43,7 @@ class TransactionEnd: Total gas consumed by this transaction. """ - output: bytes + output: Bytes """ Return value or revert reason of the outermost frame of execution. """ @@ -64,7 +66,7 @@ class PrecompileStart: Trace event that is triggered before executing a precompile. """ - address: bytes + address: Bytes """ Precompile that is about to be executed. """ diff --git a/src/ethereum/utils/hexadecimal.py b/src/ethereum/utils/hexadecimal.py index d7a4f20ee5..fb5090a957 100644 --- a/src/ethereum/utils/hexadecimal.py +++ b/src/ethereum/utils/hexadecimal.py @@ -69,7 +69,7 @@ def hex_to_bytes(hex_string: str) -> Bytes: byte_stream : `bytes` Byte stream corresponding to the given hexadecimal string. """ - return bytes.fromhex(remove_hex_prefix(hex_string)) + return Bytes.fromhex(remove_hex_prefix(hex_string)) def hex_to_bytes8(hex_string: str) -> Bytes8: @@ -86,7 +86,7 @@ def hex_to_bytes8(hex_string: str) -> Bytes8: 8_byte_stream : `Bytes8` 8-byte stream corresponding to the given hexadecimal string. """ - return Bytes8(bytes.fromhex(remove_hex_prefix(hex_string).rjust(16, "0"))) + return Bytes8(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(16, "0"))) def hex_to_bytes20(hex_string: str) -> Bytes20: @@ -103,7 +103,7 @@ def hex_to_bytes20(hex_string: str) -> Bytes20: 20_byte_stream : `Bytes20` 20-byte stream corresponding to the given hexadecimal string. """ - return Bytes20(bytes.fromhex(remove_hex_prefix(hex_string).rjust(20, "0"))) + return Bytes20(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(20, "0"))) def hex_to_bytes32(hex_string: str) -> Bytes32: @@ -120,7 +120,7 @@ def hex_to_bytes32(hex_string: str) -> Bytes32: 32_byte_stream : `Bytes32` 32-byte stream corresponding to the given hexadecimal string. """ - return Bytes32(bytes.fromhex(remove_hex_prefix(hex_string).rjust(64, "0"))) + return Bytes32(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(64, "0"))) def hex_to_bytes256(hex_string: str) -> Bytes256: @@ -138,7 +138,7 @@ def hex_to_bytes256(hex_string: str) -> Bytes256: 256-byte stream corresponding to the given hexadecimal string. """ return Bytes256( - bytes.fromhex(remove_hex_prefix(hex_string).rjust(512, "0")) + Bytes.fromhex(remove_hex_prefix(hex_string).rjust(512, "0")) ) @@ -156,7 +156,7 @@ def hex_to_hash(hex_string: str) -> Hash32: hash : `Hash32` 32-byte stream obtained from the given hexadecimal string. """ - return Hash32(bytes.fromhex(remove_hex_prefix(hex_string))) + return Hash32(Bytes.fromhex(remove_hex_prefix(hex_string))) def hex_to_uint(hex_string: str) -> Uint: From 4c991a5e094b93632c1dae81da701dce4fe11c70 Mon Sep 17 00:00:00 2001 From: Shashwat-Nautiyal Date: Wed, 23 Apr 2025 21:04:58 +0530 Subject: [PATCH 53/70] compute_header_hash() removed --- src/ethereum/arrow_glacier/fork.py | 35 -------------------------- src/ethereum/berlin/fork.py | 35 -------------------------- src/ethereum/byzantium/fork.py | 35 -------------------------- src/ethereum/cancun/fork.py | 35 -------------------------- src/ethereum/constantinople/fork.py | 35 -------------------------- src/ethereum/dao_fork/fork.py | 35 -------------------------- src/ethereum/frontier/fork.py | 35 -------------------------- src/ethereum/gray_glacier/fork.py | 35 -------------------------- src/ethereum/homestead/fork.py | 35 -------------------------- src/ethereum/istanbul/fork.py | 35 -------------------------- src/ethereum/london/fork.py | 35 -------------------------- src/ethereum/muir_glacier/fork.py | 35 -------------------------- src/ethereum/paris/fork.py | 35 -------------------------- src/ethereum/shanghai/fork.py | 35 -------------------------- src/ethereum/spurious_dragon/fork.py | 35 -------------------------- src/ethereum/tangerine_whistle/fork.py | 35 -------------------------- 16 files changed, 560 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index aa05524561..e852f1b773 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -805,41 +805,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 48e011a8ef..dfea8d4edf 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -704,41 +704,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index 9d0b6af9cf..2699fd5870 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -681,41 +681,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 2dd3999fe0..07b7dedd62 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -768,41 +768,6 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 4bd3861080..af7b360f80 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -681,41 +681,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 0b47c2bc2c..c650d797b8 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -685,41 +685,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 9288e73523..12a52aea79 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -668,41 +668,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 97491a3d9f..541a417afe 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -805,41 +805,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index a46924c605..f02b397f1f 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -668,41 +668,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 4bd3861080..af7b360f80 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -681,41 +681,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index 733e7b48e4..a9d3088652 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -811,41 +811,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index e49a26efd8..386f7c887a 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -683,41 +683,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index ba4bd90c04..c9a36dd01d 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -584,41 +584,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 7632b6a433..b177fd57aa 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -622,41 +622,6 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index e4922b2552..f4abe4950e 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -676,41 +676,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index ebbb2456cb..447fb6a5fa 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -667,41 +667,6 @@ def process_transaction( block_output.block_logs += tx_output.logs -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: """ Validates the gas limit for a block. From 5de14b56bbf88956772ec043625680540d19d26a Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Thu, 1 May 2025 13:44:11 +0200 Subject: [PATCH 54/70] remove empty account handling code (#1160) --- src/ethereum/cancun/fork.py | 12 ------ src/ethereum/cancun/state.py | 35 +----------------- src/ethereum/cancun/vm/__init__.py | 23 +----------- src/ethereum/cancun/vm/instructions/system.py | 5 --- src/ethereum/cancun/vm/interpreter.py | 18 +-------- src/ethereum/exceptions.py | 6 +++ src/ethereum/paris/fork.py | 3 -- src/ethereum/paris/state.py | 37 ++----------------- src/ethereum/paris/vm/__init__.py | 23 +----------- src/ethereum/paris/vm/instructions/system.py | 5 --- src/ethereum/paris/vm/interpreter.py | 18 +-------- src/ethereum/prague/fork.py | 12 ------ src/ethereum/prague/state.py | 35 +----------------- src/ethereum/prague/vm/__init__.py | 23 +----------- src/ethereum/prague/vm/instructions/system.py | 5 --- src/ethereum/prague/vm/interpreter.py | 19 +--------- src/ethereum/shanghai/fork.py | 3 -- src/ethereum/shanghai/state.py | 37 ++----------------- src/ethereum/shanghai/vm/__init__.py | 23 +----------- .../shanghai/vm/instructions/system.py | 5 --- src/ethereum/shanghai/vm/interpreter.py | 18 +-------- .../evm_tools/loaders/fixture_loader.py | 5 +++ .../evm_tools/loaders/fork_loader.py | 5 +++ tests/helpers/load_evm_tools_tests.py | 7 +++- tests/helpers/load_state_tests.py | 9 ++++- 25 files changed, 51 insertions(+), 340 deletions(-) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 07b7dedd62..066650b27a 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -35,7 +35,6 @@ TransientStorage, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -538,15 +537,6 @@ def process_system_transaction( system_tx_output = process_message_call(system_tx_message) - # TODO: Empty accounts in post-merge forks are impossible - # see Ethereum Improvement Proposal 7523. - # This line is only included to support invalid tests in the test suite - # and will have to be removed in the future. - # See https://github.com/ethereum/execution-specs/issues/955 - destroy_touched_empty_accounts( - block_env.state, system_tx_output.touched_accounts - ) - return system_tx_output @@ -722,8 +712,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/cancun/state.py b/src/ethereum/cancun/state.py index 4f9d5ff4b2..69aaa042d1 100644 --- a/src/ethereum/cancun/state.py +++ b/src/ethereum/cancun/state.py @@ -510,6 +510,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -556,22 +558,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -702,20 +688,3 @@ def set_transient_storage( trie_set(trie, key, value) if trie._data == {}: del transient_storage._tries[address] - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Set[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Set[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index 333135baf1..d73c4f8cde 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, VersionedHash -from ..state import State, TransientStorage, account_exists_and_is_empty +from ..state import State, TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -148,7 +147,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -170,11 +168,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -190,18 +183,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index e26b0c2d48..efbef8e861 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -546,10 +545,6 @@ def selfdestruct(evm: Evm) -> None: set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/cancun/vm/interpreter.py b/src/ethereum/cancun/vm/interpreter.py index 36a73f46b0..b120e9e6f1 100644 --- a/src/ethereum/cancun/vm/interpreter.py +++ b/src/ethereum/cancun/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -232,8 +221,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state, transient_storage) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -281,7 +268,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/exceptions.py b/src/ethereum/exceptions.py index 7fd959d9d3..4681aeed05 100644 --- a/src/ethereum/exceptions.py +++ b/src/ethereum/exceptions.py @@ -16,6 +16,12 @@ class InvalidBlock(EthereumException): """ +class StateWithEmptyAccount(EthereumException): + """ + Thrown when the state has empty account. + """ + + class InvalidTransaction(EthereumException): """ Thrown when a transaction being processed is found to be invalid. diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index c9a36dd01d..9e053340a4 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -34,7 +34,6 @@ State, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, set_account_balance, @@ -564,8 +563,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used receipt = make_receipt( diff --git a/src/ethereum/paris/state.py b/src/ethereum/paris/state.py index 3650999343..88494ab2fb 100644 --- a/src/ethereum/paris/state.py +++ b/src/ethereum/paris/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -477,6 +477,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -523,22 +525,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -611,20 +597,3 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Iterable[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Iterable[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/paris/vm/__init__.py b/src/ethereum/paris/vm/__init__.py index 633ad70954..8d0d881741 100644 --- a/src/ethereum/paris/vm/__init__.py +++ b/src/ethereum/paris/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt from ..fork_types import Address -from ..state import State, account_exists_and_is_empty +from ..state import State from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -136,7 +135,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -158,11 +156,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -178,18 +171,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/paris/vm/instructions/system.py b/src/ethereum/paris/vm/instructions/system.py index 22722995a7..41db241f69 100644 --- a/src/ethereum/paris/vm/instructions/system.py +++ b/src/ethereum/paris/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -524,10 +523,6 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/paris/vm/interpreter.py b/src/ethereum/paris/vm/interpreter.py index fe3ae8d4cc..77f6dc2f1e 100644 --- a/src/ethereum/paris/vm/interpreter.py +++ b/src/ethereum/paris/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -230,8 +219,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -279,7 +266,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index d0f80b0e0d..6ab2d1e00e 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -42,7 +42,6 @@ TransientStorage, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -572,15 +571,6 @@ def process_system_transaction( system_tx_output = process_message_call(system_tx_message) - # TODO: Empty accounts in post-merge forks are impossible - # see Ethereum Improvement Proposal 7523. - # This line is only included to support invalid tests in the test suite - # and will have to be removed in the future. - # See https://github.com/ethereum/execution-specs/issues/955 - destroy_touched_empty_accounts( - block_env.state, system_tx_output.touched_accounts - ) - return system_tx_output @@ -831,8 +821,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/prague/state.py b/src/ethereum/prague/state.py index 4f9d5ff4b2..69aaa042d1 100644 --- a/src/ethereum/prague/state.py +++ b/src/ethereum/prague/state.py @@ -510,6 +510,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -556,22 +558,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -702,20 +688,3 @@ def set_transient_storage( trie_set(trie, key, value) if trie._data == {}: del transient_storage._tries[address] - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Set[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Set[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/prague/vm/__init__.py b/src/ethereum/prague/vm/__init__.py index dab7771379..2211cfcb3e 100644 --- a/src/ethereum/prague/vm/__init__.py +++ b/src/ethereum/prague/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address, Authorization, VersionedHash -from ..state import State, TransientStorage, account_exists_and_is_empty +from ..state import State, TransientStorage from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -153,7 +152,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -175,11 +173,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -195,18 +188,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 4654774d5d..840e37c1cc 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -570,10 +569,6 @@ def selfdestruct(evm: Evm) -> None: set_account_balance(evm.message.block_env.state, originator, U256(0)) evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index 278c7a4491..81e4d92f37 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -32,7 +32,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -43,7 +42,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.eoa_delegation import set_delegation @@ -77,16 +75,14 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. - 7. `return_data`: The output of the execution. + 5. `error`: The error from the execution if any. + 6. `return_data`: The output of the execution. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] return_data: Bytes @@ -118,7 +114,6 @@ def process_message_call(message: Message) -> MessageCallOutput: U256(0), tuple(), set(), - set(), AddressCollision(), Bytes(b""), ) @@ -128,19 +123,13 @@ def process_message_call(message: Message) -> MessageCallOutput: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -153,7 +142,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, return_data=evm.output, ) @@ -243,8 +231,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state, transient_storage) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -292,7 +278,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index b177fd57aa..408a70b791 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -34,7 +34,6 @@ State, account_exists_and_is_empty, destroy_account, - destroy_touched_empty_accounts, get_account, increment_nonce, modify_state, @@ -577,8 +576,6 @@ def process_transaction( for address in tx_output.accounts_to_delete: destroy_account(block_env.state, address) - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - block_output.block_gas_used += tx_gas_used receipt = make_receipt( diff --git a/src/ethereum/shanghai/state.py b/src/ethereum/shanghai/state.py index 3650999343..88494ab2fb 100644 --- a/src/ethereum/shanghai/state.py +++ b/src/ethereum/shanghai/state.py @@ -17,7 +17,7 @@ `EMPTY_ACCOUNT`. """ from dataclasses import dataclass, field -from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.frozen import modify @@ -477,6 +477,8 @@ def modify_state( Modify an `Account` in the `State`. """ set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) def move_ether( @@ -523,22 +525,6 @@ def set_balance(account: Account) -> None: modify_state(state, address, set_balance) -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - def increment_nonce(state: State, address: Address) -> None: """ Increments the nonce of an account. @@ -611,20 +597,3 @@ def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: assert isinstance(original_value, U256) return original_value - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Iterable[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Iterable[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/shanghai/vm/__init__.py b/src/ethereum/shanghai/vm/__init__.py index ab9a558d98..4f57e478b5 100644 --- a/src/ethereum/shanghai/vm/__init__.py +++ b/src/ethereum/shanghai/vm/__init__.py @@ -24,10 +24,9 @@ from ..blocks import Log, Receipt, Withdrawal from ..fork_types import Address -from ..state import State, account_exists_and_is_empty +from ..state import State from ..transactions import LegacyTransaction from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS __all__ = ("Environment", "Evm", "Message") @@ -141,7 +140,6 @@ class Evm: message: Message output: Bytes accounts_to_delete: Set[Address] - touched_accounts: Set[Address] return_data: Bytes error: Optional[EthereumException] accessed_addresses: Set[Address] @@ -163,11 +161,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) @@ -183,18 +176,4 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 7682eb38b5..1bdd58c534 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -19,7 +19,6 @@ from ...fork_types import Address from ...state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, get_account, @@ -547,10 +546,6 @@ def selfdestruct(evm: Evm) -> None: # register account for deletion evm.accounts_to_delete.add(originator) - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - # HALT the execution evm.running = False diff --git a/src/ethereum/shanghai/vm/interpreter.py b/src/ethereum/shanghai/vm/interpreter.py index 431bda94f9..ecd04d2468 100644 --- a/src/ethereum/shanghai/vm/interpreter.py +++ b/src/ethereum/shanghai/vm/interpreter.py @@ -33,7 +33,6 @@ from ..blocks import Log from ..fork_types import Address from ..state import ( - account_exists_and_is_empty, account_has_code_or_nonce, account_has_storage, begin_transaction, @@ -44,7 +43,6 @@ move_ether, rollback_transaction, set_code, - touch_account, ) from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas @@ -77,15 +75,13 @@ class MessageCallOutput: 2. `refund_counter`: gas to refund after execution. 3. `logs`: list of `Log` generated during execution. 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. + 5. `error`: The error from the execution if any. """ gas_left: Uint refund_counter: U256 logs: Tuple[Log, ...] accounts_to_delete: Set[Address] - touched_accounts: Set[Address] error: Optional[EthereumException] @@ -112,25 +108,19 @@ def process_message_call(message: Message) -> MessageCallOutput: ) or account_has_storage(block_env.state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), U256(0), tuple(), set(), set(), AddressCollision() + Uint(0), U256(0), tuple(), set(), AddressCollision() ) else: evm = process_create_message(message) else: evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - touched_accounts = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts refund_counter += U256(evm.refund_counter) tx_end = TransactionEnd( @@ -143,7 +133,6 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter=refund_counter, logs=logs, accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, error=evm.error, ) @@ -230,8 +219,6 @@ def process_message(message: Message) -> Evm: # take snapshot of state before processing the message begin_transaction(state) - touch_account(state, message.current_target) - if message.should_transfer_value and message.value != 0: move_ether( state, message.caller, message.current_target, message.value @@ -279,7 +266,6 @@ def execute_code(message: Message) -> Evm: message=message, output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=message.accessed_addresses, diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index 621a69eda3..df11319bcb 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -11,6 +11,7 @@ from ethereum_types.numeric import U256 from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import StateWithEmptyAccount from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes8, @@ -60,6 +61,7 @@ def json_to_state(self, raw: Any) -> Any: """Converts json state data to a state object""" state = self.fork.State() set_storage = self.fork.set_storage + EMPTY_ACCOUNT = self.fork.EMPTY_ACCOUNT for address_hex, account_state in raw.items(): address = self.fork.hex_to_address(address_hex) @@ -68,6 +70,9 @@ def json_to_state(self, raw: Any) -> Any: balance=U256(hex_to_uint(account_state.get("balance", "0x0"))), code=hex_to_bytes(account_state.get("code", "")), ) + if self.fork.proof_of_stake and account == EMPTY_ACCOUNT: + raise StateWithEmptyAccount(f"Empty account at {address_hex}.") + self.fork.set_account(state, address, account) for k, v in account_state.get("storage", {}).items(): diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index f8bef3360c..0877cb812b 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -172,6 +172,11 @@ def Bloom(self) -> Any: """Bloom class of the fork""" return self._module("fork_types").Bloom + @property + def EMPTY_ACCOUNT(self) -> Any: + """EMPTY_ACCOUNT of the fork""" + return self._module("fork_types").EMPTY_ACCOUNT + @property def Header(self) -> Any: """Header class of the fork""" diff --git a/tests/helpers/load_evm_tools_tests.py b/tests/helpers/load_evm_tools_tests.py index 38af0eafff..602f72c55a 100644 --- a/tests/helpers/load_evm_tools_tests.py +++ b/tests/helpers/load_evm_tools_tests.py @@ -6,6 +6,7 @@ import pytest +from ethereum.exceptions import StateWithEmptyAccount from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools import create_parser from ethereum_spec_tools.evm_tools.statetest import read_test_cases @@ -126,7 +127,11 @@ def load_evm_tools_test(test_case: Dict[str, str], fork_name: str) -> None: ] t8n_options = parser.parse_args(t8n_args) - t8n = T8N(t8n_options, sys.stdout, in_stream) + try: + t8n = T8N(t8n_options, sys.stdout, in_stream) + except StateWithEmptyAccount as e: + pytest.xfail(str(e)) + t8n.run_state_test() assert hex_to_bytes(post_hash) == t8n.result.state_root diff --git a/tests/helpers/load_state_tests.py b/tests/helpers/load_state_tests.py index 94ada0bffa..6372065674 100644 --- a/tests/helpers/load_state_tests.py +++ b/tests/helpers/load_state_tests.py @@ -13,7 +13,7 @@ from ethereum_types.numeric import U64 from ethereum.crypto.hash import keccak256 -from ethereum.exceptions import EthereumException +from ethereum.exceptions import EthereumException, StateWithEmptyAccount from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load @@ -56,9 +56,14 @@ def run_blockchain_st_test(test_case: Dict, load: Load) -> None: genesis_rlp = hex_to_bytes(json_data["genesisRLP"]) assert rlp.encode(genesis_block) == genesis_rlp + try: + state = load.json_to_state(json_data["pre"]) + except StateWithEmptyAccount as e: + pytest.xfail(str(e)) + chain = load.fork.BlockChain( blocks=[genesis_block], - state=load.json_to_state(json_data["pre"]), + state=state, chain_id=U64(json_data["genesisBlockHeader"].get("chainId", 1)), ) From ab9058e9081917fa03e10d2a61fa26859a174d94 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Mon, 24 Jun 2024 15:01:14 -0400 Subject: [PATCH 55/70] Disallow unused arguments (closes #962) --- setup.cfg | 9 ++++++++- src/ethereum/cancun/vm/instructions/system.py | 3 --- src/ethereum/fork_criteria.py | 4 ++++ src/ethereum/prague/vm/instructions/system.py | 3 --- src/ethereum/shanghai/vm/instructions/system.py | 3 --- src/ethereum/trace.py | 16 ++++++++-------- src/ethereum_spec_tools/docc.py | 8 ++++++-- src/ethereum_spec_tools/evm_tools/daemon.py | 3 +++ .../lint/lints/glacier_forks_hygiene.py | 4 ++++ tests/conftest.py | 2 +- tests/test_rlp.py | 8 ++++---- tests/test_trace.py | 17 ++++++++--------- 12 files changed, 46 insertions(+), 34 deletions(-) diff --git a/setup.cfg b/setup.cfg index 499c9c7720..10bdb5a610 100644 --- a/setup.cfg +++ b/setup.cfg @@ -125,7 +125,7 @@ python_requires = >=3.10 install_requires = pycryptodome>=3,<4 coincurve>=20,<21 - typing_extensions>=4.2 + typing_extensions>=4.4 py-ecc>=8.0.0b2,<9 ethereum-types>=0.2.1,<0.3 ethereum-rlp>=0.1.1,<0.2 @@ -181,6 +181,7 @@ lint = flake8-bugbear==23.12.2 flake8-docstrings==1.7.0 flake8-spellcheck==0.28.0 + flake8-unused-arguments==0.0.13 tools = platformdirs>=4.2,<5 @@ -197,6 +198,7 @@ optimized = dictionaries=en_US,python,technical docstring-convention = all extend-ignore = + U101 E203 D107 D200 @@ -221,4 +223,9 @@ extend-exclude = per-file-ignores = tests/*:D100,D101,D103,D104,E501,SC100,SC200 +unused-arguments-ignore-abstract-functions = true +unused-arguments-ignore-override-functions = true +unused-arguments-ignore-overload-functions = true +unused-arguments-ignore-dunder = true + # vim: set ft=dosini: diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index efbef8e861..7b12c23c64 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -65,7 +65,6 @@ def generic_create( contract_address: Address, memory_start_position: U256, memory_size: U256, - init_code_gas: Uint, ) -> None: """ Core logic used by the `CREATE*` family of opcodes. @@ -182,7 +181,6 @@ def create(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER @@ -235,7 +233,6 @@ def create2(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER diff --git a/src/ethereum/fork_criteria.py b/src/ethereum/fork_criteria.py index 2a70b8e74f..09729553aa 100644 --- a/src/ethereum/fork_criteria.py +++ b/src/ethereum/fork_criteria.py @@ -29,6 +29,7 @@ from typing import Final, Literal, SupportsInt, Tuple from ethereum_types.numeric import U256, Uint +from typing_extensions import override @functools.total_ordering @@ -130,6 +131,7 @@ def __init__(self, block_number: SupportsInt): self._internal = (ForkCriteria.BLOCK_NUMBER, int(block_number)) self.block_number = Uint(int(block_number)) + @override def check(self, block_number: Uint, timestamp: U256) -> bool: """ Check whether the block number has been reached. @@ -162,6 +164,7 @@ def __init__(self, timestamp: SupportsInt): self._internal = (ForkCriteria.TIMESTAMP, int(timestamp)) self.timestamp = U256(timestamp) + @override def check(self, block_number: Uint, timestamp: U256) -> bool: """ Check whether the timestamp has been reached. @@ -188,6 +191,7 @@ class Unscheduled(ForkCriteria): def __init__(self) -> None: self._internal = (ForkCriteria.UNSCHEDULED, 0) + @override def check(self, block_number: Uint, timestamp: U256) -> Literal[False]: """ Unscheduled forks never occur; always returns `False`. diff --git a/src/ethereum/prague/vm/instructions/system.py b/src/ethereum/prague/vm/instructions/system.py index 840e37c1cc..ea9be98391 100644 --- a/src/ethereum/prague/vm/instructions/system.py +++ b/src/ethereum/prague/vm/instructions/system.py @@ -66,7 +66,6 @@ def generic_create( contract_address: Address, memory_start_position: U256, memory_size: U256, - init_code_gas: Uint, ) -> None: """ Core logic used by the `CREATE*` family of opcodes. @@ -184,7 +183,6 @@ def create(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER @@ -237,7 +235,6 @@ def create2(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER diff --git a/src/ethereum/shanghai/vm/instructions/system.py b/src/ethereum/shanghai/vm/instructions/system.py index 1bdd58c534..ae758367c5 100644 --- a/src/ethereum/shanghai/vm/instructions/system.py +++ b/src/ethereum/shanghai/vm/instructions/system.py @@ -64,7 +64,6 @@ def generic_create( contract_address: Address, memory_start_position: U256, memory_size: U256, - init_code_gas: Uint, ) -> None: """ Core logic used by the `CREATE*` family of opcodes. @@ -181,7 +180,6 @@ def create(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER @@ -234,7 +232,6 @@ def create2(evm: Evm) -> None: contract_address, memory_start_position, memory_size, - init_code_gas, ) # PROGRAM COUNTER diff --git a/src/ethereum/trace.py b/src/ethereum/trace.py index 223f96e5b7..3b12eb9374 100644 --- a/src/ethereum/trace.py +++ b/src/ethereum/trace.py @@ -170,11 +170,11 @@ class GasAndRefund: def discard_evm_trace( - evm: object, - event: TraceEvent, - trace_memory: bool = False, - trace_stack: bool = True, - trace_return_data: bool = False, + evm: object, # noqa: U100 + event: TraceEvent, # noqa: U100 + trace_memory: bool = False, # noqa: U100 + trace_stack: bool = True, # noqa: U100 + trace_return_data: bool = False, # noqa: U100 ) -> None: """ An [`EvmTracer`] that discards all events. @@ -200,9 +200,9 @@ def __call__( evm: object, event: TraceEvent, /, - trace_memory: bool = False, - trace_stack: bool = True, - trace_return_data: bool = False, + trace_memory: bool = False, # noqa: U100 + trace_stack: bool = True, # noqa: U100 + trace_return_data: bool = False, # noqa: U100 ) -> None: """ Call `self` as a function, recording a trace event. diff --git a/src/ethereum_spec_tools/docc.py b/src/ethereum_spec_tools/docc.py index 897be21207..c066f350e8 100644 --- a/src/ethereum_spec_tools/docc.py +++ b/src/ethereum_spec_tools/docc.py @@ -53,7 +53,7 @@ from fladrif.treediff import Adapter, Operation, TreeMatcher from mistletoe import block_token as blocks # type: ignore from mistletoe import span_token as spans -from typing_extensions import assert_never +from typing_extensions import assert_never, override from .forks import Hardfork @@ -775,6 +775,7 @@ def enter(self, node: Node) -> Visit: parent.replace_child(flex, new_node) return Visit.TraverseChildren + @override def exit(self, node: Node) -> None: self._stack.pop() @@ -908,11 +909,13 @@ def insert(self, afters: Sequence[Node]) -> None: ) ) + @override def equal(self, before: Sequence[Node], after: Sequence[Node]) -> None: parent = self.stack[-1] for node in after: parent.add(node) + @override def descend(self, before: Node, after: Node) -> None: parent = self.stack[-1] node = _DoccApply.FlexNode(after) @@ -1000,12 +1003,13 @@ def enter(self, node: Node) -> Visit: return Visit.SkipChildren + @override def exit(self, node: Node) -> None: self._stack.pop() def render_diff( - context: object, + context: object, # noqa: U100 parent: object, diff: object, ) -> html.RenderResult: diff --git a/src/ethereum_spec_tools/evm_tools/daemon.py b/src/ethereum_spec_tools/evm_tools/daemon.py index e0bc26f2ca..1e42aaa3a5 100644 --- a/src/ethereum_spec_tools/evm_tools/daemon.py +++ b/src/ethereum_spec_tools/evm_tools/daemon.py @@ -14,6 +14,8 @@ from typing import Any, Tuple, Union from urllib.parse import parse_qs, urlparse +from typing_extensions import override + def daemon_arguments(subparsers: argparse._SubParsersAction) -> None: """ @@ -30,6 +32,7 @@ def daemon_arguments(subparsers: argparse._SubParsersAction) -> None: class _EvmToolHandler(BaseHTTPRequestHandler): + @override def log_request( self, code: int | str = "-", size: int | str = "-" ) -> None: diff --git a/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py b/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py index 81bf0403ee..9c84c793df 100644 --- a/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py +++ b/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py @@ -7,6 +7,8 @@ import sys from typing import Dict, List, Sequence +from typing_extensions import override + from ethereum_spec_tools.forks import Hardfork from ethereum_spec_tools.lint import ( Diagnostic, @@ -193,12 +195,14 @@ def visit_Module(self, module: ast.Module) -> None: for item in module.__dict__["body"]: self.visit(item) + @override def visit_Import(self, import_: ast.Import) -> None: """ Visit an Import """ pass + @override def visit_ImportFrom(self, import_from: ast.ImportFrom) -> None: """ Visit an Import From diff --git a/tests/conftest.py b/tests/conftest.py index ab5eed5e7b..d7e7d5b176 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -114,7 +114,7 @@ def git_clone_fixtures(url: str, commit_hash: str, location: str) -> None: submodule.update(init=True, recursive=True) -def pytest_sessionstart(session: Session) -> None: +def pytest_sessionstart(session: Session) -> None: # noqa: U100 for _, props in TEST_FIXTURES.items(): fixture_path = props["fixture_path"] diff --git a/tests/test_rlp.py b/tests/test_rlp.py index 1a845d43a2..5e914c8402 100644 --- a/tests/test_rlp.py +++ b/tests/test_rlp.py @@ -67,22 +67,22 @@ def test_ethtest_fixtures_for_rlp_encoding( @pytest.mark.parametrize( - "raw_data, encoded_data", + "_raw_data, encoded_data", ethtest_fixtures_as_pytest_fixtures("RandomRLPTests/example.json"), ) def test_ethtest_fixtures_for_successfully_rlp_decoding( - raw_data: Bytes, encoded_data: Bytes + _raw_data: Extended, encoded_data: Bytes ) -> None: decoded_data = rlp.decode(encoded_data) assert rlp.encode(decoded_data) == encoded_data @pytest.mark.parametrize( - "raw_data, encoded_data", + "_raw_data, encoded_data", ethtest_fixtures_as_pytest_fixtures("invalidRLPTest.json"), ) def test_ethtest_fixtures_for_fails_in_rlp_decoding( - raw_data: Bytes, encoded_data: Bytes + _raw_data: Bytes, encoded_data: Bytes ) -> None: with pytest.raises(rlp.DecodingError): rlp.decode(encoded_data) diff --git a/tests/test_trace.py b/tests/test_trace.py index 11445d4b60..004aa434a5 100644 --- a/tests/test_trace.py +++ b/tests/test_trace.py @@ -10,21 +10,21 @@ def test_modify_evm_trace() -> None: trace2: Optional[ethereum.trace.TraceEvent] = None def tracer1( - evm: object, + evm: object, # noqa: U100 event: ethereum.trace.TraceEvent, - trace_memory: bool = False, - trace_stack: bool = True, - trace_return_data: bool = False, + trace_memory: bool = False, # noqa: U100 + trace_stack: bool = True, # noqa: U100 + trace_return_data: bool = False, # noqa: U100 ) -> None: nonlocal trace1 trace1 = event def tracer2( - evm: object, + evm: object, # noqa: U100 event: ethereum.trace.TraceEvent, - trace_memory: bool = False, - trace_stack: bool = True, - trace_return_data: bool = False, + trace_memory: bool = False, # noqa: U100 + trace_stack: bool = True, # noqa: U100 + trace_return_data: bool = False, # noqa: U100 ) -> None: nonlocal trace2 trace2 = event @@ -47,7 +47,6 @@ def tracer2( message=cast(Message, object()), output=b"", accounts_to_delete=set(), - touched_accounts=set(), return_data=b"", error=None, accessed_addresses=set(), From 32b73268e13e19c1e9694e152e7e74aa0d99607b Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Fri, 2 May 2025 02:54:56 -0400 Subject: [PATCH 56/70] fix docc --- src/ethereum/prague/requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index d7ce9a5e4a..6ed423188f 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -18,7 +18,7 @@ from ethereum.utils.hexadecimal import hex_to_bytes32 from .blocks import decode_receipt -from .state import trie_get +from .trie import trie_get from .utils.hexadecimal import hex_to_address from .vm import BlockOutput From f524bae8fa13bce1e86ed21f9d964f5676c5acdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Fri, 6 Dec 2024 17:39:17 +0100 Subject: [PATCH 57/70] Import Extended type directly instead of using rlp.Extended --- src/ethereum/arrow_glacier/trie.py | 46 ++++++++++++------------ src/ethereum/berlin/trie.py | 46 ++++++++++++------------ src/ethereum/byzantium/trie.py | 46 ++++++++++++------------ src/ethereum/cancun/trie.py | 48 +++++++++++++------------- src/ethereum/constantinople/trie.py | 46 ++++++++++++------------ src/ethereum/dao_fork/trie.py | 46 ++++++++++++------------ src/ethereum/frontier/trie.py | 46 ++++++++++++------------ src/ethereum/gray_glacier/trie.py | 46 ++++++++++++------------ src/ethereum/homestead/trie.py | 46 ++++++++++++------------ src/ethereum/istanbul/trie.py | 46 ++++++++++++------------ src/ethereum/london/trie.py | 46 ++++++++++++------------ src/ethereum/muir_glacier/trie.py | 46 ++++++++++++------------ src/ethereum/paris/trie.py | 46 ++++++++++++------------ src/ethereum/prague/trie.py | 48 +++++++++++++------------- src/ethereum/shanghai/trie.py | 48 +++++++++++++------------- src/ethereum/spurious_dragon/trie.py | 46 ++++++++++++------------ src/ethereum/tangerine_whistle/trie.py | 46 ++++++++++++------------ 17 files changed, 394 insertions(+), 394 deletions(-) diff --git a/src/ethereum/arrow_glacier/trie.py b/src/ethereum/arrow_glacier/trie.py index 79954e4b6f..2b09d4c771 100644 --- a/src/ethereum/arrow_glacier/trie.py +++ b/src/ethereum/arrow_glacier/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/berlin/trie.py b/src/ethereum/berlin/trie.py index 790d7a34f4..306e930c4b 100644 --- a/src/ethereum/berlin/trie.py +++ b/src/ethereum/berlin/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/byzantium/trie.py b/src/ethereum/byzantium/trie.py index db489e4a7c..8f618e0026 100644 --- a/src/ethereum/byzantium/trie.py +++ b/src/ethereum/byzantium/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/cancun/trie.py b/src/ethereum/cancun/trie.py index 1eed02b5b1..8afe1281c0 100644 --- a/src/ethereum/cancun/trie.py +++ b/src/ethereum/cancun/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -86,7 +86,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -95,26 +95,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -124,13 +124,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,10 +146,10 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: Returns ------- - encoded : `rlp.Extended` + encoded : `Extended` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -489,6 +489,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/constantinople/trie.py b/src/ethereum/constantinople/trie.py index 9aac219680..9999eefb76 100644 --- a/src/ethereum/constantinople/trie.py +++ b/src/ethereum/constantinople/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/dao_fork/trie.py b/src/ethereum/dao_fork/trie.py index 11d3ab4bbe..83623062c5 100644 --- a/src/ethereum/dao_fork/trie.py +++ b/src/ethereum/dao_fork/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/frontier/trie.py b/src/ethereum/frontier/trie.py index f2dee88b1c..c7dec95462 100644 --- a/src/ethereum/frontier/trie.py +++ b/src/ethereum/frontier/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -82,7 +82,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -91,26 +91,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -120,13 +120,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -145,7 +145,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -487,6 +487,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/gray_glacier/trie.py b/src/ethereum/gray_glacier/trie.py index dfa243d098..881f87aaec 100644 --- a/src/ethereum/gray_glacier/trie.py +++ b/src/ethereum/gray_glacier/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/homestead/trie.py b/src/ethereum/homestead/trie.py index f641e61cdd..16e4cfcf92 100644 --- a/src/ethereum/homestead/trie.py +++ b/src/ethereum/homestead/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/istanbul/trie.py b/src/ethereum/istanbul/trie.py index 524a18e90e..902e723197 100644 --- a/src/ethereum/istanbul/trie.py +++ b/src/ethereum/istanbul/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/london/trie.py b/src/ethereum/london/trie.py index 82737ede77..8d13a6ab46 100644 --- a/src/ethereum/london/trie.py +++ b/src/ethereum/london/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/muir_glacier/trie.py b/src/ethereum/muir_glacier/trie.py index 3790a5599a..1fe5248ff7 100644 --- a/src/ethereum/muir_glacier/trie.py +++ b/src/ethereum/muir_glacier/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/paris/trie.py b/src/ethereum/paris/trie.py index 5b07566e09..65ee9ea8ae 100644 --- a/src/ethereum/paris/trie.py +++ b/src/ethereum/paris/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.RLP` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/prague/trie.py b/src/ethereum/prague/trie.py index c81ef86b61..0c6cbdba30 100644 --- a/src/ethereum/prague/trie.py +++ b/src/ethereum/prague/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -86,7 +86,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -95,26 +95,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -124,13 +124,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,10 +146,10 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: Returns ------- - encoded : `rlp.Extended` + encoded : `Extended` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -489,6 +489,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/shanghai/trie.py b/src/ethereum/shanghai/trie.py index f90800826d..f1c9e1d11f 100644 --- a/src/ethereum/shanghai/trie.py +++ b/src/ethereum/shanghai/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -86,7 +86,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -95,26 +95,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -124,13 +124,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,10 +146,10 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: Returns ------- - encoded : `rlp.Extended` + encoded : `Extended` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -489,6 +489,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/spurious_dragon/trie.py b/src/ethereum/spurious_dragon/trie.py index af5e2d97ec..7e6747f410 100644 --- a/src/ethereum/spurious_dragon/trie.py +++ b/src/ethereum/spurious_dragon/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.Extended` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) diff --git a/src/ethereum/tangerine_whistle/trie.py b/src/ethereum/tangerine_whistle/trie.py index 1f95b2c2e4..037bc516df 100644 --- a/src/ethereum/tangerine_whistle/trie.py +++ b/src/ethereum/tangerine_whistle/trie.py @@ -30,7 +30,7 @@ cast, ) -from ethereum_rlp import rlp +from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint @@ -83,7 +83,7 @@ class LeafNode: """Leaf node in the Merkle Trie""" rest_of_key: Bytes - value: rlp.Extended + value: Extended @slotted_freezable @@ -92,26 +92,26 @@ class ExtensionNode: """Extension node in the Merkle Trie""" key_segment: Bytes - subnode: rlp.Extended + subnode: Extended BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, ] @@ -121,13 +121,13 @@ class BranchNode: """Branch node in the Merkle Trie""" subnodes: BranchSubnodes - value: rlp.Extended + value: Extended InternalNode = Union[LeafNode, ExtensionNode, BranchNode] -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: +def encode_internal_node(node: Optional[InternalNode]) -> Extended: """ Encodes a Merkle Trie node into its RLP form. The RLP will then be serialized into a `Bytes` and hashed unless it is less that 32 bytes @@ -146,7 +146,7 @@ def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: encoded : `rlp.Extended` The node encoded as RLP. """ - unencoded: rlp.Extended + unencoded: Extended if node is None: unencoded = b"" elif isinstance(node, LeafNode): @@ -486,6 +486,6 @@ def patricialize( for k in range(16) ) return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), value, ) From 7ee273471d3f5295235a2a661ed41251055c883e Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Mon, 5 May 2025 15:45:35 -0600 Subject: [PATCH 58/70] feat: add initial EIP-7805 impl --- src/ethereum/prague/blocks.py | 1 + src/ethereum/prague/fork.py | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index 80b29087e9..d445fd59c5 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -82,6 +82,7 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 6ab2d1e00e..bd6f7b2cda 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -12,6 +12,7 @@ Entry point for the Ethereum specification. """ +from copy import deepcopy from dataclasses import dataclass from typing import List, Optional, Tuple, Union @@ -216,6 +217,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, + inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -578,6 +580,7 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], + inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -597,6 +600,8 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. + inclusion_list : + Transactions that must be included in the block if possible. Returns ------- @@ -620,6 +625,13 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) + validate_inclusion_list( + block_env, + block_output, + transactions, + inclusion_list, + ) + process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -865,6 +877,35 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) +def validate_inclusion_list( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + transactions: Tuple[Union[Bytes, LegacyTransaction], ...], + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], +) -> None: + """ + Validate the block satisfies the inclusion list. + """ + + for tx in inclusion_list: + # If the transaction is already present in the block, then skip. + if tx in transactions: + continue + + block_env = deepcopy(block_env) + block_output = deepcopy(block_output) + try: + tx = decode_transaction(tx) + index = Uint(len(transactions)) + process_transaction(block_env, block_output, tx, index) + except Exception: + continue + else: + # If the transaction was not in the block and was decoded and + # executed successfully, then mark the block invalid. + raise InvalidBlock + + def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. From 4efe7640da7cb1a8746477151e9d6a01608504c9 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Tue, 25 Mar 2025 09:53:25 +0100 Subject: [PATCH 59/70] osaka initial commit --- src/ethereum/osaka/__init__.py | 7 + src/ethereum/osaka/blocks.py | 136 +++ src/ethereum/osaka/bloom.py | 85 ++ src/ethereum/osaka/exceptions.py | 24 + src/ethereum/osaka/fork.py | 962 ++++++++++++++++++ src/ethereum/osaka/fork_types.py | 79 ++ src/ethereum/osaka/requests.py | 74 ++ src/ethereum/osaka/state.py | 720 +++++++++++++ src/ethereum/osaka/transactions.py | 579 +++++++++++ src/ethereum/osaka/trie.py | 494 +++++++++ src/ethereum/osaka/utils/__init__.py | 3 + src/ethereum/osaka/utils/address.py | 93 ++ src/ethereum/osaka/utils/hexadecimal.py | 68 ++ src/ethereum/osaka/utils/message.py | 95 ++ src/ethereum/osaka/vm/__init__.py | 209 ++++ src/ethereum/osaka/vm/eoa_delegation.py | 214 ++++ src/ethereum/osaka/vm/exceptions.py | 140 +++ src/ethereum/osaka/vm/gas.py | 369 +++++++ .../osaka/vm/instructions/__init__.py | 366 +++++++ .../osaka/vm/instructions/arithmetic.py | 373 +++++++ src/ethereum/osaka/vm/instructions/bitwise.py | 240 +++++ src/ethereum/osaka/vm/instructions/block.py | 255 +++++ .../osaka/vm/instructions/comparison.py | 178 ++++ .../osaka/vm/instructions/control_flow.py | 171 ++++ .../osaka/vm/instructions/environment.py | 597 +++++++++++ src/ethereum/osaka/vm/instructions/keccak.py | 64 ++ src/ethereum/osaka/vm/instructions/log.py | 88 ++ src/ethereum/osaka/vm/instructions/memory.py | 177 ++++ src/ethereum/osaka/vm/instructions/stack.py | 209 ++++ src/ethereum/osaka/vm/instructions/storage.py | 184 ++++ src/ethereum/osaka/vm/instructions/system.py | 755 ++++++++++++++ src/ethereum/osaka/vm/interpreter.py | 328 ++++++ src/ethereum/osaka/vm/memory.py | 81 ++ .../vm/precompiled_contracts/__init__.py | 54 + .../vm/precompiled_contracts/alt_bn128.py | 154 +++ .../osaka/vm/precompiled_contracts/blake2f.py | 41 + .../bls12_381/__init__.py | 583 +++++++++++ .../bls12_381/bls12_381_g1.py | 148 +++ .../bls12_381/bls12_381_g2.py | 148 +++ .../bls12_381/bls12_381_pairing.py | 67 ++ .../vm/precompiled_contracts/ecrecover.py | 63 ++ .../vm/precompiled_contracts/identity.py | 38 + .../osaka/vm/precompiled_contracts/mapping.py | 74 ++ .../osaka/vm/precompiled_contracts/modexp.py | 169 +++ .../precompiled_contracts/point_evaluation.py | 72 ++ .../vm/precompiled_contracts/ripemd160.py | 43 + .../osaka/vm/precompiled_contracts/sha256.py | 40 + src/ethereum/osaka/vm/runtime.py | 67 ++ src/ethereum/osaka/vm/stack.py | 59 ++ tests/osaka/__init__.py | 0 tests/osaka/test_evm_tools.py | 66 ++ tests/osaka/test_rlp.py | 167 +++ tests/osaka/test_state_transition.py | 104 ++ tests/osaka/test_trie.py | 89 ++ 54 files changed, 10663 insertions(+) create mode 100644 src/ethereum/osaka/__init__.py create mode 100644 src/ethereum/osaka/blocks.py create mode 100644 src/ethereum/osaka/bloom.py create mode 100644 src/ethereum/osaka/exceptions.py create mode 100644 src/ethereum/osaka/fork.py create mode 100644 src/ethereum/osaka/fork_types.py create mode 100644 src/ethereum/osaka/requests.py create mode 100644 src/ethereum/osaka/state.py create mode 100644 src/ethereum/osaka/transactions.py create mode 100644 src/ethereum/osaka/trie.py create mode 100644 src/ethereum/osaka/utils/__init__.py create mode 100644 src/ethereum/osaka/utils/address.py create mode 100644 src/ethereum/osaka/utils/hexadecimal.py create mode 100644 src/ethereum/osaka/utils/message.py create mode 100644 src/ethereum/osaka/vm/__init__.py create mode 100644 src/ethereum/osaka/vm/eoa_delegation.py create mode 100644 src/ethereum/osaka/vm/exceptions.py create mode 100644 src/ethereum/osaka/vm/gas.py create mode 100644 src/ethereum/osaka/vm/instructions/__init__.py create mode 100644 src/ethereum/osaka/vm/instructions/arithmetic.py create mode 100644 src/ethereum/osaka/vm/instructions/bitwise.py create mode 100644 src/ethereum/osaka/vm/instructions/block.py create mode 100644 src/ethereum/osaka/vm/instructions/comparison.py create mode 100644 src/ethereum/osaka/vm/instructions/control_flow.py create mode 100644 src/ethereum/osaka/vm/instructions/environment.py create mode 100644 src/ethereum/osaka/vm/instructions/keccak.py create mode 100644 src/ethereum/osaka/vm/instructions/log.py create mode 100644 src/ethereum/osaka/vm/instructions/memory.py create mode 100644 src/ethereum/osaka/vm/instructions/stack.py create mode 100644 src/ethereum/osaka/vm/instructions/storage.py create mode 100644 src/ethereum/osaka/vm/instructions/system.py create mode 100644 src/ethereum/osaka/vm/interpreter.py create mode 100644 src/ethereum/osaka/vm/memory.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/__init__.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/blake2f.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/identity.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/mapping.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/modexp.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/sha256.py create mode 100644 src/ethereum/osaka/vm/runtime.py create mode 100644 src/ethereum/osaka/vm/stack.py create mode 100644 tests/osaka/__init__.py create mode 100644 tests/osaka/test_evm_tools.py create mode 100644 tests/osaka/test_rlp.py create mode 100644 tests/osaka/test_state_transition.py create mode 100644 tests/osaka/test_trie.py diff --git a/src/ethereum/osaka/__init__.py b/src/ethereum/osaka/__init__.py new file mode 100644 index 0000000000..8c5c37298a --- /dev/null +++ b/src/ethereum/osaka/__init__.py @@ -0,0 +1,7 @@ +""" +The Osaka fork. +""" + +from ethereum.fork_criteria import Unscheduled + +FORK_CRITERIA = Unscheduled() diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py new file mode 100644 index 0000000000..15208d4ad0 --- /dev/null +++ b/src/ethereum/osaka/blocks.py @@ -0,0 +1,136 @@ +""" +A `Block` is a single link in the chain that is Ethereum. Each `Block` contains +a `Header` and zero or more transactions. Each `Header` contains associated +metadata like the block number, parent block hash, and how much gas was +consumed by its transactions. + +Together, these blocks form a cryptographically secure journal recording the +history of all state transitions that have happened since the genesis of the +chain. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes8, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint + +from ..crypto.hash import Hash32 +from .fork_types import Address, Bloom, Root +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + SetCodeTransaction, + Transaction, +) + + +@slotted_freezable +@dataclass +class Withdrawal: + """ + Withdrawals that have been validated on the consensus layer. + """ + + index: U64 + validator_index: U64 + address: Address + amount: U256 + + +@slotted_freezable +@dataclass +class Header: + """ + Header portion of a block on the chain. + """ + + parent_hash: Hash32 + ommers_hash: Hash32 + coinbase: Address + state_root: Root + transactions_root: Root + receipt_root: Root + bloom: Bloom + difficulty: Uint + number: Uint + gas_limit: Uint + gas_used: Uint + timestamp: U256 + extra_data: Bytes + prev_randao: Bytes32 + nonce: Bytes8 + base_fee_per_gas: Uint + withdrawals_root: Root + blob_gas_used: U64 + excess_blob_gas: U64 + parent_beacon_block_root: Root + requests_hash: Hash32 + + +@slotted_freezable +@dataclass +class Block: + """ + A complete block. + """ + + header: Header + transactions: Tuple[Union[Bytes, LegacyTransaction], ...] + ommers: Tuple[Header, ...] + withdrawals: Tuple[Withdrawal, ...] + + +@slotted_freezable +@dataclass +class Log: + """ + Data record produced during the execution of a transaction. + """ + + address: Address + topics: Tuple[Hash32, ...] + data: bytes + + +@slotted_freezable +@dataclass +class Receipt: + """ + Result of a transaction. + """ + + succeeded: bool + cumulative_gas_used: Uint + bloom: Bloom + logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(receipt) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2, 3, 4) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/osaka/bloom.py b/src/ethereum/osaka/bloom.py new file mode 100644 index 0000000000..0ba6e431ab --- /dev/null +++ b/src/ethereum/osaka/bloom.py @@ -0,0 +1,85 @@ +""" +Ethereum Logs Bloom +^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +This modules defines functions for calculating bloom filters of logs. For the +general theory of bloom filters see e.g. `Wikipedia +`_. Bloom filters are used to allow +for efficient searching of logs by address and/or topic, by rapidly +eliminating blocks and receipts from their search. +""" + +from typing import Tuple + +from ethereum_types.numeric import Uint + +from ethereum.crypto.hash import keccak256 + +from .blocks import Log +from .fork_types import Bloom + + +def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: + """ + Add a bloom entry to the bloom filter (`bloom`). + + The number of hash functions used is 3. They are calculated by taking the + least significant 11 bits from the first 3 16-bit words of the + `keccak_256()` hash of `bloom_entry`. + + Parameters + ---------- + bloom : + The bloom filter. + bloom_entry : + An entry which is to be added to bloom filter. + """ + hash = keccak256(bloom_entry) + + for idx in (0, 2, 4): + # Obtain the least significant 11 bits from the pair of bytes + # (16 bits), and set this bit in bloom bytearray. + # The obtained bit is 0-indexed in the bloom filter from the least + # significant bit to the most significant bit. + bit_to_set = Uint.from_be_bytes(hash[idx : idx + 2]) & Uint(0x07FF) + # Below is the index of the bit in the bytearray (where 0-indexed + # byte is the most significant byte) + bit_index = 0x07FF - int(bit_to_set) + + byte_index = bit_index // 8 + bit_value = 1 << (7 - (bit_index % 8)) + bloom[byte_index] = bloom[byte_index] | bit_value + + +def logs_bloom(logs: Tuple[Log, ...]) -> Bloom: + """ + Obtain the logs bloom from a list of log entries. + + The address and each topic of a log are added to the bloom filter. + + Parameters + ---------- + logs : + List of logs for which the logs bloom is to be obtained. + + Returns + ------- + logs_bloom : `Bloom` + The logs bloom obtained which is 256 bytes with some bits set as per + the caller address and the log topics. + """ + bloom: bytearray = bytearray(b"\x00" * 256) + + for log in logs: + add_to_bloom(bloom, log.address) + for topic in log.topics: + add_to_bloom(bloom, topic) + + return Bloom(bloom) diff --git a/src/ethereum/osaka/exceptions.py b/src/ethereum/osaka/exceptions.py new file mode 100644 index 0000000000..5781a2c1c3 --- /dev/null +++ b/src/ethereum/osaka/exceptions.py @@ -0,0 +1,24 @@ +""" +Exceptions specific to this fork. +""" + +from typing import Final + +from ethereum.exceptions import InvalidTransaction + + +class TransactionTypeError(InvalidTransaction): + """ + Unknown [EIP-2718] transaction type byte. + + [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + """ + + transaction_type: Final[int] + """ + The type byte of the transaction that caused the error. + """ + + def __init__(self, transaction_type: int): + super().__init__(f"unknown transaction type `{transaction_type}`") + self.transaction_type = transaction_type diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py new file mode 100644 index 0000000000..50b5033e53 --- /dev/null +++ b/src/ethereum/osaka/fork.py @@ -0,0 +1,962 @@ +""" +Ethereum Specification +^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Entry point for the Ethereum specification. +""" + +from dataclasses import dataclass +from typing import List, Optional, Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import ( + EthereumException, + InvalidBlock, + InvalidSenderError, +) + +from . import vm +from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt +from .bloom import logs_bloom +from .fork_types import Account, Address, Authorization, VersionedHash +from .requests import ( + CONSOLIDATION_REQUEST_TYPE, + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + compute_requests_hash, + parse_deposit_requests_from_receipt, +) +from .state import ( + State, + TransientStorage, + account_exists_and_is_empty, + destroy_account, + destroy_touched_empty_accounts, + get_account, + increment_nonce, + modify_state, + set_account_balance, + state_root, +) +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + SetCodeTransaction, + Transaction, + decode_transaction, + encode_transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set +from .utils.hexadecimal import hex_to_address +from .utils.message import prepare_message +from .vm import Message +from .vm.eoa_delegation import is_valid_delegation +from .vm.gas import ( + calculate_blob_gas_price, + calculate_data_fee, + calculate_excess_blob_gas, + calculate_total_blob_gas, +) +from .vm.interpreter import MessageCallOutput, process_message_call + +BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) +ELASTICITY_MULTIPLIER = Uint(2) +GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) +GAS_LIMIT_MINIMUM = Uint(5000) +EMPTY_OMMER_HASH = keccak256(rlp.encode([])) +SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") +BEACON_ROOTS_ADDRESS = hex_to_address( + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" +) +SYSTEM_TRANSACTION_GAS = Uint(30000000) +MAX_BLOB_GAS_PER_BLOCK = Uint(1179648) +VERSIONED_HASH_VERSION_KZG = b"\x01" + +WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x00000961Ef480Eb55e80D19ad83579A64c007002" +) +CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x0000BBdDc7CE488642fb579F8B00f3a590007251" +) +HISTORY_STORAGE_ADDRESS = hex_to_address( + "0x0000F90827F1C53a10cb7A02335B175320002935" +) +HISTORY_SERVE_WINDOW = 8192 + + +@dataclass +class BlockChain: + """ + History and current state of the block chain. + """ + + blocks: List[Block] + state: State + chain_id: U64 + + +def apply_fork(old: BlockChain) -> BlockChain: + """ + Transforms the state from the previous hard fork (`old`) into the block + chain object for this hard fork and returns it. + + When forks need to implement an irregular state transition, this function + is used to handle the irregularity. See the :ref:`DAO Fork ` for + an example. + + Parameters + ---------- + old : + Previous block chain object. + + Returns + ------- + new : `BlockChain` + Upgraded block chain object for this hard fork. + """ + return old + + +def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: + """ + Obtain the list of hashes of the previous 256 blocks in order of + increasing block number. + + This function will return less hashes for the first 256 blocks. + + The ``BLOCKHASH`` opcode needs to access the latest hashes on the chain, + therefore this function retrieves them. + + Parameters + ---------- + chain : + History and current state. + + Returns + ------- + recent_block_hashes : `List[Hash32]` + Hashes of the recent 256 blocks in order of increasing block number. + """ + recent_blocks = chain.blocks[-255:] + # TODO: This function has not been tested rigorously + if len(recent_blocks) == 0: + return [] + + recent_block_hashes = [] + + for block in recent_blocks: + prev_block_hash = block.header.parent_hash + recent_block_hashes.append(prev_block_hash) + + # We are computing the hash only for the most recent block and not for + # the rest of the blocks as they have successors which have the hash of + # the current block as parent hash. + most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header)) + recent_block_hashes.append(most_recent_block_hash) + + return recent_block_hashes + + +def state_transition(chain: BlockChain, block: Block) -> None: + """ + Attempts to apply a block to an existing block chain. + + All parts of the block's contents need to be verified before being added + to the chain. Blocks are verified by ensuring that the contents of the + block make logical sense with the contents of the parent block. The + information in the block's header must also match the corresponding + information in the block. + + To implement Ethereum, in theory clients are only required to store the + most recent 255 blocks of the chain since as far as execution is + concerned, only those blocks are accessed. Practically, however, clients + should store more blocks to handle reorgs. + + Parameters + ---------- + chain : + History and current state. + block : + Block to apply to `chain`. + """ + validate_header(chain, block.header) + if block.ommers != (): + raise InvalidBlock + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + excess_blob_gas=block.header.excess_blob_gas, + parent_beacon_block_root=block.header.parent_beacon_block_root, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + withdrawals=block.withdrawals, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + withdrawals_root = root(block_output.withdrawals_trie) + requests_hash = compute_requests_hash(block_output.requests) + + if block_output.block_gas_used != block.header.gas_used: + raise InvalidBlock( + f"{block_output.block_gas_used} != {block.header.gas_used}" + ) + if transactions_root != block.header.transactions_root: + raise InvalidBlock + if block_state_root != block.header.state_root: + raise InvalidBlock + if receipt_root != block.header.receipt_root: + raise InvalidBlock + if block_logs_bloom != block.header.bloom: + raise InvalidBlock + if withdrawals_root != block.header.withdrawals_root: + raise InvalidBlock + if block_output.blob_gas_used != block.header.blob_gas_used: + raise InvalidBlock + if requests_hash != block.header.requests_hash: + raise InvalidBlock + + chain.blocks.append(block) + if len(chain.blocks) > 255: + # Real clients have to store more blocks to deal with reorgs, but the + # protocol only requires the last 255 + chain.blocks = chain.blocks[-255:] + + +def calculate_base_fee_per_gas( + block_gas_limit: Uint, + parent_gas_limit: Uint, + parent_gas_used: Uint, + parent_base_fee_per_gas: Uint, +) -> Uint: + """ + Calculates the base fee per gas for the block. + + Parameters + ---------- + block_gas_limit : + Gas limit of the block for which the base fee is being calculated. + parent_gas_limit : + Gas limit of the parent block. + parent_gas_used : + Gas used in the parent block. + parent_base_fee_per_gas : + Base fee per gas of the parent block. + + Returns + ------- + base_fee_per_gas : `Uint` + Base fee per gas for the block. + """ + parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER + if not check_gas_limit(block_gas_limit, parent_gas_limit): + raise InvalidBlock + + if parent_gas_used == parent_gas_target: + expected_base_fee_per_gas = parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = max( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR, + Uint(1), + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas + base_fee_per_gas_delta + ) + else: + gas_used_delta = parent_gas_target - parent_gas_used + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = ( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas - base_fee_per_gas_delta + ) + + return Uint(expected_base_fee_per_gas) + + +def validate_header(chain: BlockChain, header: Header) -> None: + """ + Verifies a block header. + + In order to consider a block's header valid, the logic for the + quantities in the header should match the logic for the block itself. + For example the header timestamp should be greater than the block's parent + timestamp because the block was created *after* the parent block. + Additionally, the block's number should be directly following the parent + block's number since it is the next block in the sequence. + + Parameters + ---------- + chain : + History and current state. + header : + Header to check for correctness. + """ + if header.number < Uint(1): + raise InvalidBlock + parent_header_number = header.number - Uint(1) + first_block_number = chain.blocks[0].header.number + last_block_number = chain.blocks[-1].header.number + + if ( + parent_header_number < first_block_number + or parent_header_number > last_block_number + ): + raise InvalidBlock + + parent_header = chain.blocks[ + parent_header_number - first_block_number + ].header + + excess_blob_gas = calculate_excess_blob_gas(parent_header) + if header.excess_blob_gas != excess_blob_gas: + raise InvalidBlock + + if header.gas_used > header.gas_limit: + raise InvalidBlock + + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_header.base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: + raise InvalidBlock + if header.number != parent_header.number + Uint(1): + raise InvalidBlock + if len(header.extra_data) > 32: + raise InvalidBlock + if header.difficulty != 0: + raise InvalidBlock + if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00": + raise InvalidBlock + if header.ommers_hash != EMPTY_OMMER_HASH: + raise InvalidBlock + + block_parent_hash = keccak256(rlp.encode(parent_header)) + if header.parent_hash != block_parent_hash: + raise InvalidBlock + + +def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: + """ + Check if the transaction is includable in the block. + + Parameters + ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. + tx : + The transaction. + + Returns + ------- + sender_address : + The sender of the transaction. + effective_gas_price : + The price to charge for gas when the transaction is executed. + blob_versioned_hashes : + The blob versioned hashes of the transaction. + tx_blob_gas_used: + The blob gas used by the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not includable. + """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used + blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used + + if tx.gas > gas_available: + raise InvalidBlock + + tx_blob_gas_used = calculate_total_blob_gas(tx) + if tx_blob_gas_used > blob_gas_available: + raise InvalidBlock + + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + if isinstance( + tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) + ): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise InvalidBlock + if tx.max_fee_per_gas < block_env.base_fee_per_gas: + raise InvalidBlock + + priority_fee_per_gas = min( + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, + ) + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas + else: + if tx.gas_price < block_env.base_fee_per_gas: + raise InvalidBlock + effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if isinstance(tx, BlobTransaction): + if len(tx.blob_versioned_hashes) == 0: + raise InvalidBlock + for blob_versioned_hash in tx.blob_versioned_hashes: + if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: + raise InvalidBlock + + blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) + if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: + raise InvalidBlock + + max_gas_fee += calculate_total_blob_gas(tx) * Uint( + tx.max_fee_per_blob_gas + ) + blob_versioned_hashes = tx.blob_versioned_hashes + else: + blob_versioned_hashes = () + + if isinstance(tx, (BlobTransaction, SetCodeTransaction)): + if not isinstance(tx.to, Address): + raise InvalidBlock + + if isinstance(tx, SetCodeTransaction): + if not any(tx.authorizations): + raise InvalidBlock + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code and not is_valid_delegation(sender_account.code): + raise InvalidSenderError("not EOA") + + return ( + sender_address, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) + + +def make_receipt( + tx: Transaction, + error: Optional[EthereumException], + cumulative_gas_used: Uint, + logs: Tuple[Log, ...], +) -> Union[Bytes, Receipt]: + """ + Make the receipt for a transaction that was executed. + + Parameters + ---------- + tx : + The executed transaction. + error : + Error in the top level frame of the transaction, if any. + cumulative_gas_used : + The total gas used so far in the block after the transaction was + executed. + logs : + The logs produced by the transaction. + + Returns + ------- + receipt : + The receipt for the transaction. + """ + receipt = Receipt( + succeeded=error is None, + cumulative_gas_used=cumulative_gas_used, + bloom=logs_bloom(logs), + logs=logs, + ) + + return encode_receipt(tx, receipt) + + +def process_system_transaction( + block_env: vm.BlockEnvironment, + target_address: Address, + data: Bytes, +) -> MessageCallOutput: + """ + Process a system transaction. + + Parameters + ---------- + block_env : + The block scoped environment. + target_address : + Address of the contract to call. + data : + Data to pass to the contract. + + Returns + ------- + system_tx_output : `MessageCallOutput` + Output of processing the system transaction. + """ + system_contract_code = get_account(block_env.state, target_address).code + + tx_env = vm.TransactionEnvironment( + origin=SYSTEM_ADDRESS, + gas_price=block_env.base_fee_per_gas, + gas=SYSTEM_TRANSACTION_GAS, + access_list_addresses=set(), + access_list_storage_keys=set(), + transient_storage=TransientStorage(), + blob_versioned_hashes=(), + authorizations=(), + index_in_block=None, + tx_hash=None, + traces=[], + ) + + system_tx_message = Message( + block_env=block_env, + tx_env=tx_env, + caller=SYSTEM_ADDRESS, + target=target_address, + gas=SYSTEM_TRANSACTION_GAS, + value=U256(0), + data=data, + code=system_contract_code, + depth=Uint(0), + current_target=target_address, + code_address=target_address, + should_transfer_value=False, + is_static=False, + accessed_addresses=set(), + accessed_storage_keys=set(), + parent_evm=None, + ) + + system_tx_output = process_message_call(system_tx_message) + + # TODO: Empty accounts in post-merge forks are impossible + # see Ethereum Improvement Proposal 7523. + # This line is only included to support invalid tests in the test suite + # and will have to be removed in the future. + # See https://github.com/ethereum/execution-specs/issues/955 + destroy_touched_empty_accounts( + block_env.state, system_tx_output.touched_accounts + ) + + return system_tx_output + + +def apply_body( + block_env: vm.BlockEnvironment, + transactions: Tuple[Union[LegacyTransaction, Bytes], ...], + withdrawals: Tuple[Withdrawal, ...], +) -> vm.BlockOutput: + """ + Executes a block. + + Many of the contents of a block are stored in data structures called + tries. There is a transactions trie which is similar to a ledger of the + transactions stored in the current block. There is also a receipts trie + which stores the results of executing a transaction, like the post state + and gas used. This function creates and executes the block that is to be + added to the chain. + + Parameters + ---------- + block_env : + The block scoped environment. + transactions : + Transactions included in the block. + withdrawals : + Withdrawals to be processed in the current block. + + Returns + ------- + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() + + process_system_transaction( + block_env=block_env, + target_address=BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, + ) + + process_system_transaction( + block_env=block_env, + target_address=HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash + ) + + for i, tx in enumerate(map(decode_transaction, transactions)): + process_transaction(block_env, block_output, tx, Uint(i)) + + process_withdrawals(block_env, block_output, withdrawals) + + process_general_purpose_requests( + block_env=block_env, + block_output=block_output, + ) + + return block_output + + +def process_general_purpose_requests( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, +) -> None: + """ + Process all the requests in the block. + + Parameters + ---------- + block_env : + The execution environment for the Block. + block_output : + The block output for the current block. + """ + # Requests are to be in ascending order of request type + deposit_requests = block_output.deposit_requests + requests_from_execution = block_output.requests + if len(deposit_requests) > 0: + requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) + + system_withdrawal_tx_output = process_system_transaction( + block_env=block_env, + target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + data=b"", + ) + + if len(system_withdrawal_tx_output.return_data) > 0: + requests_from_execution.append( + WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data + ) + + system_consolidation_tx_output = process_system_transaction( + block_env=block_env, + target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + data=b"", + ) + + if len(system_consolidation_tx_output.return_data) > 0: + requests_from_execution.append( + CONSOLIDATION_REQUEST_TYPE + + system_consolidation_tx_output.return_data + ) + + +def process_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: + """ + Execute a transaction against the provided environment. + + This function processes the actions needed to execute a transaction. + It decrements the sender's account after calculating the gas fee and + refunds them the proper amount after execution. Calling contracts, + deploying code, and incrementing nonces are all examples of actions that + happen within this function or from a call made within this function. + + Accounts that are marked for deletion are processed and destroyed after + execution. + + Parameters + ---------- + block_env : + Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. + tx : + Transaction to execute. + index: + Index of the transaction in the block. + """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + + intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + + ( + sender, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) + + if isinstance(tx, BlobTransaction): + blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) + else: + blob_gas_fee = Uint(0) + + effective_gas_fee = tx.gas * effective_gas_price + + gas = tx.gas - intrinsic_gas + increment_nonce(block_env.state, 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) + ) + + access_list_addresses = set() + access_list_storage_keys = set() + access_list_addresses.add(block_env.coinbase) + if isinstance( + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), + ): + for address, keys in tx.access_list: + access_list_addresses.add(address) + for key in keys: + access_list_storage_keys.add((address, key)) + + authorizations: Tuple[Authorization, ...] = () + if isinstance(tx, SetCodeTransaction): + authorizations = tx.authorizations + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + transient_storage=TransientStorage(), + blob_versioned_hashes=blob_versioned_hashes, + authorizations=authorizations, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) + + # For EIP-7623 we first calculate the execution_gas_used, which includes + # the execution gas refund. + execution_gas_used = tx.gas - tx_output.gas_left + gas_refund = min( + execution_gas_used // Uint(5), Uint(tx_output.refund_counter) + ) + execution_gas_used -= gas_refund + + # Transactions with less execution_gas_used than the floor pay at the + # floor cost. + tx_gas_used = max(execution_gas_used, calldata_floor_gas_cost) + + tx_output.gas_left = tx.gas - tx_gas_used + gas_refund_amount = tx_output.gas_left * effective_gas_price + + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + transaction_fee = tx_gas_used * 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) + + # transfer miner fees + coinbase_balance_after_mining_fee = get_account( + block_env.state, block_env.coinbase + ).balance + U256(transaction_fee) + if coinbase_balance_after_mining_fee != 0: + set_account_balance( + block_env.state, + 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) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) + + block_output.block_gas_used += tx_gas_used + block_output.blob_gas_used += tx_blob_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + trie_set( + block_output.receipts_trie, + rlp.encode(Uint(index)), + receipt, + ) + + block_output.block_logs += tx_output.logs + + block_output.deposit_requests += parse_deposit_requests_from_receipt( + receipt + ) + + +def process_withdrawals( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + withdrawals: Tuple[Withdrawal, ...], +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) + + modify_state(block_env.state, wd.address, increase_recipient_balance) + + if account_exists_and_is_empty(block_env.state, wd.address): + destroy_account(block_env.state, wd.address) + + +def compute_header_hash(header: Header) -> Hash32: + """ + Computes the hash of a block header. + + The header hash of a block is the canonical hash that is used to refer + to a specific block and completely distinguishes a block from another. + + ``keccak256`` is a function that produces a 256 bit hash of any input. + It also takes in any number of bytes as an input and produces a single + hash for them. A hash is a completely unique output for a single input. + So an input corresponds to one unique hash that can be used to identify + the input exactly. + + Prior to using the ``keccak256`` hash function, the header must be + encoded using the Recursive-Length Prefix. See :ref:`rlp`. + RLP encoding the header converts it into a space-efficient format that + allows for easy transfer of data between nodes. The purpose of RLP is to + encode arbitrarily nested arrays of binary data, and RLP is the primary + encoding method used to serialize objects in Ethereum's execution layer. + The only purpose of RLP is to encode structure; encoding specific data + types (e.g. strings, floats) is left up to higher-order protocols. + + Parameters + ---------- + header : + Header of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the header. + """ + return keccak256(rlp.encode(header)) + + +def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: + """ + Validates the gas limit for a block. + + The bounds of the gas limit, ``max_adjustment_delta``, is set as the + quotient of the parent block's gas limit and the + ``GAS_LIMIT_ADJUSTMENT_FACTOR``. Therefore, if the gas limit that is + passed through as a parameter is greater than or equal to the *sum* of + the parent's gas and the adjustment delta then the limit for gas is too + high and fails this function's check. Similarly, if the limit is less + than or equal to the *difference* of the parent's gas and the adjustment + delta *or* the predefined ``GAS_LIMIT_MINIMUM`` then this function's + check fails because the gas limit doesn't allow for a sufficient or + reasonable amount of gas to be used on a block. + + Parameters + ---------- + gas_limit : + Gas limit to validate. + + parent_gas_limit : + Gas limit of the parent block. + + Returns + ------- + check : `bool` + True if gas limit constraints are satisfied, False otherwise. + """ + max_adjustment_delta = parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR + if gas_limit >= parent_gas_limit + max_adjustment_delta: + return False + if gas_limit <= parent_gas_limit - max_adjustment_delta: + return False + if gas_limit < GAS_LIMIT_MINIMUM: + return False + + return True diff --git a/src/ethereum/osaka/fork_types.py b/src/ethereum/osaka/fork_types.py new file mode 100644 index 0000000000..26b6656190 --- /dev/null +++ b/src/ethereum/osaka/fork_types.py @@ -0,0 +1,79 @@ +""" +Ethereum Types +^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Types re-used throughout the specification, which are specific to Ethereum. +""" + +from dataclasses import dataclass + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes20, Bytes256 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U8, U64, U256, Uint + +from ..crypto.hash import Hash32, keccak256 + +Address = Bytes20 +Root = Hash32 +VersionedHash = Hash32 + +Bloom = Bytes256 + + +@slotted_freezable +@dataclass +class Account: + """ + State associated with an address. + """ + + nonce: Uint + balance: U256 + code: bytes + + +EMPTY_ACCOUNT = Account( + nonce=Uint(0), + balance=U256(0), + code=bytearray(), +) + + +def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: + """ + Encode `Account` dataclass. + + Storage is not stored in the `Account` dataclass, so `Accounts` cannot be + encoded without providing a storage root. + """ + return rlp.encode( + ( + raw_account_data.nonce, + raw_account_data.balance, + storage_root, + keccak256(raw_account_data.code), + ) + ) + + +@slotted_freezable +@dataclass +class Authorization: + """ + The authorization for a set code transaction. + """ + + chain_id: U256 + address: Address + nonce: U64 + y_parity: U8 + r: U256 + s: U256 diff --git a/src/ethereum/osaka/requests.py b/src/ethereum/osaka/requests.py new file mode 100644 index 0000000000..a72f8f35ae --- /dev/null +++ b/src/ethereum/osaka/requests.py @@ -0,0 +1,74 @@ +""" +Requests were introduced in EIP-7685 as a a general purpose framework for +storing contract-triggered requests. It extends the execution header and +body with a single field each to store the request information. +This inherently exposes the requests to the consensus layer, which can +then process each one. + +[EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 +""" + +from hashlib import sha256 +from typing import List, Union + +from ethereum_types.bytes import Bytes + +from .blocks import Receipt, decode_receipt +from .utils.hexadecimal import hex_to_address + +DEPOSIT_CONTRACT_ADDRESS = hex_to_address( + "0x00000000219ab540356cbb839cbe05303d7705fa" +) +DEPOSIT_REQUEST_TYPE = b"\x00" +WITHDRAWAL_REQUEST_TYPE = b"\x01" +CONSOLIDATION_REQUEST_TYPE = b"\x02" + + +def extract_deposit_data(data: Bytes) -> Bytes: + """ + Extracts Deposit Request from the DepositContract.DepositEvent data. + """ + return ( + data[192:240] # public_key + + data[288:320] # withdrawal_credentials + + data[352:360] # amount + + data[416:512] # signature + + data[544:552] # index + ) + + +def parse_deposit_requests_from_receipt( + receipt: Union[Bytes, Receipt], +) -> Bytes: + """ + Parse deposit requests from a receipt. + """ + deposit_requests: Bytes = b"" + decoded_receipt = decode_receipt(receipt) + for log in decoded_receipt.logs: + if log.address == DEPOSIT_CONTRACT_ADDRESS: + request = extract_deposit_data(log.data) + deposit_requests += request + + return deposit_requests + + +def compute_requests_hash(requests: List[Bytes]) -> Bytes: + """ + Get the hash of the requests using the SHA2-256 algorithm. + + Parameters + ---------- + requests : Bytes + The requests to hash. + + Returns + ------- + requests_hash : Bytes + The hash of the requests. + """ + m = sha256() + for request in requests: + m.update(sha256(request).digest()) + + return m.digest() diff --git a/src/ethereum/osaka/state.py b/src/ethereum/osaka/state.py new file mode 100644 index 0000000000..8a0e14728e --- /dev/null +++ b/src/ethereum/osaka/state.py @@ -0,0 +1,720 @@ +""" +State +^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state contains all information that is preserved between transactions. + +It consists of a main account trie and storage tries for each contract. + +There is a distinction between an account that does not exist and +`EMPTY_ACCOUNT`. +""" +from dataclasses import dataclass, field +from typing import Callable, Dict, List, Optional, Set, Tuple + +from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.frozen import modify +from ethereum_types.numeric import U256, Uint + +from .fork_types import EMPTY_ACCOUNT, Account, Address, Root +from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set + + +@dataclass +class State: + """ + Contains all information that is preserved between transactions. + """ + + _main_trie: Trie[Address, Optional[Account]] = field( + default_factory=lambda: Trie(secured=True, default=None) + ) + _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( + default_factory=dict + ) + _snapshots: List[ + Tuple[ + Trie[Address, Optional[Account]], + Dict[Address, Trie[Bytes32, U256]], + ] + ] = field(default_factory=list) + created_accounts: Set[Address] = field(default_factory=set) + + +@dataclass +class TransientStorage: + """ + Contains all information that is preserved between message calls + within a transaction. + """ + + _tries: Dict[Address, Trie[Bytes32, U256]] = field(default_factory=dict) + _snapshots: List[Dict[Address, Trie[Bytes32, U256]]] = field( + default_factory=list + ) + + +def close_state(state: State) -> None: + """ + Free resources held by the state. Used by optimized implementations to + release file descriptors. + """ + del state._main_trie + del state._storage_tries + del state._snapshots + del state.created_accounts + + +def begin_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Start a state transaction. + + Transactions are entirely implicit and can be nested. It is not possible to + calculate the state root during a transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.append( + ( + copy_trie(state._main_trie), + {k: copy_trie(t) for (k, t) in state._storage_tries.items()}, + ) + ) + transient_storage._snapshots.append( + {k: copy_trie(t) for (k, t) in transient_storage._tries.items()} + ) + + +def commit_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Commit a state transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._snapshots.pop() + + +def rollback_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Rollback a state transaction, resetting the state to the point when the + corresponding `start_transaction()` call was made. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._main_trie, state._storage_tries = state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._tries = transient_storage._snapshots.pop() + + +def get_account(state: State, address: Address) -> Account: + """ + Get the `Account` object at an address. Returns `EMPTY_ACCOUNT` if there + is no account at the address. + + Use `get_account_optional()` if you care about the difference between a + non-existent account and `EMPTY_ACCOUNT`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = get_account_optional(state, address) + if isinstance(account, Account): + return account + else: + return EMPTY_ACCOUNT + + +def get_account_optional(state: State, address: Address) -> Optional[Account]: + """ + Get the `Account` object at an address. Returns `None` (rather than + `EMPTY_ACCOUNT`) if there is no account at the address. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = trie_get(state._main_trie, address) + return account + + +def set_account( + state: State, address: Address, account: Optional[Account] +) -> None: + """ + Set the `Account` object at an address. Setting to `None` deletes + the account (but not its storage, see `destroy_account()`). + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to set. + account : `Account` + Account to set at address. + """ + trie_set(state._main_trie, address, account) + + +def destroy_account(state: State, address: Address) -> None: + """ + Completely remove the account at `address` and all of its storage. + + This function is made available exclusively for the `SELFDESTRUCT` + opcode. It is expected that `SELFDESTRUCT` will be disabled in a future + hardfork and this function will be removed. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account to destroy. + """ + destroy_storage(state, address) + set_account(state, address, None) + + +def destroy_storage(state: State, address: Address) -> None: + """ + Completely remove the storage at `address`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account whose storage is to be deleted. + """ + if address in state._storage_tries: + del state._storage_tries[address] + + +def mark_account_created(state: State, address: Address) -> None: + """ + Mark an account as having been created in the current transaction. + This information is used by `get_storage_original()` to handle an obscure + edgecase. + + The marker is not removed even if the account creation reverts. Since the + account cannot have had code prior to its creation and can't call + `get_storage_original()`, this is harmless. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account that has been created. + """ + state.created_accounts.add(address) + + +def get_storage(state: State, address: Address, key: Bytes32) -> U256: + """ + Get a value at a storage key on an account. Returns `U256(0)` if the + storage key has not been set previously. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + + Returns + ------- + value : `U256` + Value at the key. + """ + trie = state._storage_tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_storage( + state: State, address: Address, key: Bytes32, value: U256 +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + assert trie_get(state._main_trie, address) is not None + + trie = state._storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + state._storage_tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del state._storage_tries[address] + + +def storage_root(state: State, address: Address) -> Root: + """ + Calculate the storage root of an account. + + Parameters + ---------- + state: + The state + address : + Address of the account. + + Returns + ------- + root : `Root` + Storage root of the account. + """ + assert not state._snapshots + if address in state._storage_tries: + return root(state._storage_tries[address]) + else: + return EMPTY_TRIE_ROOT + + +def state_root(state: State) -> Root: + """ + Calculate the state root. + + Parameters + ---------- + state: + The current state. + + Returns + ------- + root : `Root` + The state root. + """ + assert not state._snapshots + + def get_storage_root(address: Address) -> Root: + return storage_root(state, address) + + return root(state._main_trie, get_storage_root=get_storage_root) + + +def account_exists(state: State, address: Address) -> bool: + """ + Checks if an account exists in the state trie + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + account_exists : `bool` + True if account exists in the state trie, False otherwise + """ + return get_account_optional(state, address) is not None + + +def account_has_code_or_nonce(state: State, address: Address) -> bool: + """ + Checks if an account has non zero nonce or non empty code + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_code_or_nonce : `bool` + True if the account has non zero nonce or non empty code, + False otherwise. + """ + account = get_account(state, address) + return account.nonce != Uint(0) or account.code != b"" + + +def account_has_storage(state: State, address: Address) -> bool: + """ + Checks if an account has storage. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_storage : `bool` + True if the account has storage, False otherwise. + """ + return address in state._storage_tries + + +def is_account_empty(state: State, address: Address) -> bool: + """ + Checks if an account has zero nonce, empty code and zero balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_empty : `bool` + True if if an account has zero nonce, empty code and zero balance, + False otherwise. + """ + account = get_account(state, address) + return ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def account_exists_and_is_empty(state: State, address: Address) -> bool: + """ + Checks if an account exists and has zero nonce, empty code and zero + balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + exists_and_is_empty : `bool` + True if an account exists and has zero nonce, empty code and zero + balance, False otherwise. + """ + account = get_account_optional(state, address) + return ( + account is not None + and account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def is_account_alive(state: State, address: Address) -> bool: + """ + Check whether is an account is both in the state and non empty. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_alive : `bool` + True if the account is alive. + """ + account = get_account_optional(state, address) + if account is None: + return False + else: + return not ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def modify_state( + state: State, address: Address, f: Callable[[Account], None] +) -> None: + """ + Modify an `Account` in the `State`. + """ + set_account(state, address, modify(get_account(state, address), f)) + + +def move_ether( + state: State, + sender_address: Address, + recipient_address: Address, + amount: U256, +) -> None: + """ + Move funds between accounts. + """ + + def reduce_sender_balance(sender: Account) -> None: + if sender.balance < amount: + raise AssertionError + sender.balance -= amount + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += amount + + modify_state(state, sender_address, reduce_sender_balance) + modify_state(state, recipient_address, increase_recipient_balance) + + +def set_account_balance(state: State, address: Address, amount: U256) -> None: + """ + Sets the balance of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + + amount: + The amount that needs to set in balance. + """ + + def set_balance(account: Account) -> None: + account.balance = amount + + modify_state(state, address, set_balance) + + +def touch_account(state: State, address: Address) -> None: + """ + Initializes an account to state. + + Parameters + ---------- + state: + The current state. + + address: + The address of the account that need to initialised. + """ + if not account_exists(state, address): + set_account(state, address, EMPTY_ACCOUNT) + + +def increment_nonce(state: State, address: Address) -> None: + """ + Increments the nonce of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + """ + + def increase_nonce(sender: Account) -> None: + sender.nonce += Uint(1) + + modify_state(state, address, increase_nonce) + + +def set_code(state: State, address: Address, code: Bytes) -> None: + """ + Sets Account code. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose code needs to be update. + + code: + The bytecode that needs to be set. + """ + + def write_code(sender: Account) -> None: + sender.code = code + + modify_state(state, address, write_code) + + +def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: + """ + Get the original value in a storage slot i.e. the value before the current + transaction began. This function reads the value from the snapshots taken + before executing the transaction. + + Parameters + ---------- + state: + The current state. + address: + Address of the account to read the value from. + key: + Key of the storage slot. + """ + # In the transaction where an account is created, its preexisting storage + # is ignored. + if address in state.created_accounts: + return U256(0) + + _, original_trie = state._snapshots[0] + original_account_trie = original_trie.get(address) + + if original_account_trie is None: + original_value = U256(0) + else: + original_value = trie_get(original_account_trie, key) + + assert isinstance(original_value, U256) + + return original_value + + +def get_transient_storage( + transient_storage: TransientStorage, address: Address, key: Bytes32 +) -> U256: + """ + Get a value at a storage key on an account from transient storage. + Returns `U256(0)` if the storage key has not been set previously. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + Returns + ------- + value : `U256` + Value at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_transient_storage( + transient_storage: TransientStorage, + address: Address, + key: Bytes32, + value: U256, +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + transient_storage._tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del transient_storage._tries[address] + + +def destroy_touched_empty_accounts( + state: State, touched_accounts: Set[Address] +) -> None: + """ + Destroy all touched accounts that are empty. + Parameters + ---------- + state: `State` + The current state. + touched_accounts: `Set[Address]` + All the accounts that have been touched in the current transaction. + """ + for address in touched_accounts: + if account_exists_and_is_empty(state, address): + destroy_account(state, address) diff --git a/src/ethereum/osaka/transactions.py b/src/ethereum/osaka/transactions.py new file mode 100644 index 0000000000..3e5adcfb46 --- /dev/null +++ b/src/ethereum/osaka/transactions.py @@ -0,0 +1,579 @@ +""" +Transactions are atomic units of work created externally to Ethereum and +submitted to be executed. If Ethereum is viewed as a state machine, +transactions are the events that move between states. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint, ulen + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction + +from .exceptions import TransactionTypeError +from .fork_types import Address, Authorization, VersionedHash + +TX_BASE_COST = Uint(21000) +FLOOR_CALLDATA_COST = Uint(10) +STANDARD_CALLDATA_TOKEN_COST = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) + + +@slotted_freezable +@dataclass +class LegacyTransaction: + """ + Atomic operation performed on the block chain. + """ + + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + v: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class AccessListTransaction: + """ + The transaction type added in EIP-2930 to support access lists. + """ + + chain_id: U64 + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class FeeMarketTransaction: + """ + The transaction type added in EIP-1559. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class BlobTransaction: + """ + The transaction type added in EIP-4844. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + max_fee_per_blob_gas: U256 + blob_versioned_hashes: Tuple[VersionedHash, ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class SetCodeTransaction: + """ + The transaction type added in EIP-7702. + """ + + chain_id: U64 + nonce: U64 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] + authorizations: Tuple[Authorization, ...] + y_parity: U256 + r: U256 + s: U256 + + +Transaction = Union[ + LegacyTransaction, + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, +] + + +def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]: + """ + Encode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, LegacyTransaction): + return tx + elif isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(tx) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(tx) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(tx) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(tx) + else: + raise Exception(f"Unable to encode transaction of type {type(tx)}") + + +def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: + """ + Decode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, Bytes): + if tx[0] == 1: + return rlp.decode_to(AccessListTransaction, tx[1:]) + elif tx[0] == 2: + return rlp.decode_to(FeeMarketTransaction, tx[1:]) + elif tx[0] == 3: + return rlp.decode_to(BlobTransaction, tx[1:]) + elif tx[0] == 4: + return rlp.decode_to(SetCodeTransaction, tx[1:]) + else: + raise TransactionTypeError(tx[0]) + else: + return tx + + +def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost charged to the transaction + based on the calldata size. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. + """ + from .vm.interpreter import MAX_CODE_SIZE + + intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) + if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + raise InvalidTransaction("Insufficient gas") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidTransaction("Nonce too high") + if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: + raise InvalidTransaction("Code size too large") + + return intrinsic_gas, calldata_floor_gas_cost + + +def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost used by the transaction + based on the calldata size. + """ + from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST + from .vm.gas import init_code_cost + + zero_bytes = 0 + for byte in tx.data: + if byte == 0: + zero_bytes += 1 + + tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4) + # EIP-7623 floor price (note: no EVM costs) + calldata_floor_gas_cost = ( + tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST + ) + + data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) + else: + create_cost = Uint(0) + + access_list_cost = Uint(0) + if isinstance( + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), + ): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + auth_cost = Uint(0) + if isinstance(tx, SetCodeTransaction): + auth_cost += Uint(PER_EMPTY_ACCOUNT_COST * len(tx.authorizations)) + + return ( + Uint( + TX_BASE_COST + + data_cost + + create_cost + + access_list_cost + + auth_cost + ), + calldata_floor_gas_cost, + ) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + elif isinstance(tx, BlobTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_4844(tx) + ) + elif isinstance(tx, SetCodeTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_7702(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_4844(tx: BlobTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-4844 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x03" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.max_fee_per_blob_gas, + tx.blob_versioned_hashes, + ) + ) + ) + + +def signing_hash_7702(tx: SetCodeTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-7702 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x04" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.authorizations, + ) + ) + ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/osaka/trie.py b/src/ethereum/osaka/trie.py new file mode 100644 index 0000000000..c81ef86b61 --- /dev/null +++ b/src/ethereum/osaka/trie.py @@ -0,0 +1,494 @@ +""" +State Trie +^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state trie is the structure responsible for storing +`.fork_types.Account` objects. +""" + +import copy +from dataclasses import dataclass, field +from typing import ( + Callable, + Dict, + Generic, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + cast, +) + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U256, Uint +from typing_extensions import assert_type + +from ethereum.cancun import trie as previous_trie +from ethereum.crypto.hash import keccak256 +from ethereum.utils.hexadecimal import hex_to_bytes + +from .blocks import Receipt, Withdrawal +from .fork_types import Account, Address, Root, encode_account +from .transactions import LegacyTransaction + +# note: an empty trie (regardless of whether it is secured) has root: +# +# keccak256(RLP(b'')) +# == +# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 # noqa: E501,SC10 +# +# also: +# +# keccak256(RLP(())) +# == +# 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 # noqa: E501,SC10 +# +# which is the sha3Uncles hash in block header with no uncles +EMPTY_TRIE_ROOT = Root( + hex_to_bytes( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) +) + +Node = Union[ + Account, Bytes, LegacyTransaction, Receipt, Uint, U256, Withdrawal, None +] +K = TypeVar("K", bound=Bytes) +V = TypeVar( + "V", + Optional[Account], + Optional[Bytes], + Bytes, + Optional[Union[LegacyTransaction, Bytes]], + Optional[Union[Receipt, Bytes]], + Optional[Union[Withdrawal, Bytes]], + Uint, + U256, +) + + +@slotted_freezable +@dataclass +class LeafNode: + """Leaf node in the Merkle Trie""" + + rest_of_key: Bytes + value: rlp.Extended + + +@slotted_freezable +@dataclass +class ExtensionNode: + """Extension node in the Merkle Trie""" + + key_segment: Bytes + subnode: rlp.Extended + + +BranchSubnodes = Tuple[ + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, + rlp.Extended, +] + + +@slotted_freezable +@dataclass +class BranchNode: + """Branch node in the Merkle Trie""" + + subnodes: BranchSubnodes + value: rlp.Extended + + +InternalNode = Union[LeafNode, ExtensionNode, BranchNode] + + +def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: + """ + Encodes a Merkle Trie node into its RLP form. The RLP will then be + serialized into a `Bytes` and hashed unless it is less that 32 bytes + when serialized. + + This function also accepts `None`, representing the absence of a node, + which is encoded to `b""`. + + Parameters + ---------- + node : Optional[InternalNode] + The node to encode. + + Returns + ------- + encoded : `rlp.Extended` + The node encoded as RLP. + """ + unencoded: rlp.Extended + if node is None: + unencoded = b"" + elif isinstance(node, LeafNode): + unencoded = ( + nibble_list_to_compact(node.rest_of_key, True), + node.value, + ) + elif isinstance(node, ExtensionNode): + unencoded = ( + nibble_list_to_compact(node.key_segment, False), + node.subnode, + ) + elif isinstance(node, BranchNode): + unencoded = list(node.subnodes) + [node.value] + else: + raise AssertionError(f"Invalid internal node type {type(node)}!") + + encoded = rlp.encode(unencoded) + if len(encoded) < 32: + return unencoded + else: + return keccak256(encoded) + + +def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: + """ + Encode a Node for storage in the Merkle Trie. + + Currently mostly an unimplemented stub. + """ + if isinstance(node, Account): + assert storage_root is not None + return encode_account(node, storage_root) + elif isinstance(node, (LegacyTransaction, Receipt, Withdrawal, U256)): + return rlp.encode(node) + elif isinstance(node, Bytes): + return node + else: + return previous_trie.encode_node(node, storage_root) + + +@dataclass +class Trie(Generic[K, V]): + """ + The Merkle Trie. + """ + + secured: bool + default: V + _data: Dict[K, V] = field(default_factory=dict) + + +def copy_trie(trie: Trie[K, V]) -> Trie[K, V]: + """ + Create a copy of `trie`. Since only frozen objects may be stored in tries, + the contents are reused. + + Parameters + ---------- + trie: `Trie` + Trie to copy. + + Returns + ------- + new_trie : `Trie[K, V]` + A copy of the trie. + """ + return Trie(trie.secured, trie.default, copy.copy(trie._data)) + + +def trie_set(trie: Trie[K, V], key: K, value: V) -> None: + """ + Stores an item in a Merkle Trie. + + This method deletes the key if `value == trie.default`, because the Merkle + Trie represents the default value by omitting it from the trie. + + Parameters + ---------- + trie: `Trie` + Trie to store in. + key : `Bytes` + Key to lookup. + value : `V` + Node to insert at `key`. + """ + if value == trie.default: + if key in trie._data: + del trie._data[key] + else: + trie._data[key] = value + + +def trie_get(trie: Trie[K, V], key: K) -> V: + """ + Gets an item from the Merkle Trie. + + This method returns `trie.default` if the key is missing. + + Parameters + ---------- + trie: + Trie to lookup in. + key : + Key to lookup. + + Returns + ------- + node : `V` + Node at `key` in the trie. + """ + return trie._data.get(key, trie.default) + + +def common_prefix_length(a: Sequence, b: Sequence) -> int: + """ + Find the longest common prefix of two sequences. + """ + for i in range(len(a)): + if i >= len(b) or a[i] != b[i]: + return i + return len(a) + + +def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes: + """ + Compresses nibble-list into a standard byte array with a flag. + + A nibble-list is a list of byte values no greater than `15`. The flag is + encoded in high nibble of the highest byte. The flag nibble can be broken + down into two two-bit flags. + + Highest nibble:: + + +---+---+----------+--------+ + | _ | _ | is_leaf | parity | + +---+---+----------+--------+ + 3 2 1 0 + + + The lowest bit of the nibble encodes the parity of the length of the + remaining nibbles -- `0` when even and `1` when odd. The second lowest bit + is used to distinguish leaf and extension nodes. The other two bits are not + used. + + Parameters + ---------- + x : + Array of nibbles. + is_leaf : + True if this is part of a leaf node, or false if it is an extension + node. + + Returns + ------- + compressed : `bytearray` + Compact byte array. + """ + compact = bytearray() + + if len(x) % 2 == 0: # ie even length + compact.append(16 * (2 * is_leaf)) + for i in range(0, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + else: + compact.append(16 * ((2 * is_leaf) + 1) + x[0]) + for i in range(1, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + + return Bytes(compact) + + +def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: + """ + Converts a `Bytes` into to a sequence of nibbles (bytes with value < 16). + + Parameters + ---------- + bytes_: + The `Bytes` to convert. + + Returns + ------- + nibble_list : `Bytes` + The `Bytes` in nibble-list format. + """ + nibble_list = bytearray(2 * len(bytes_)) + for byte_index, byte in enumerate(bytes_): + nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 + nibble_list[byte_index * 2 + 1] = byte & 0x0F + return Bytes(nibble_list) + + +def _prepare_trie( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Mapping[Bytes, Bytes]: + """ + Prepares the trie for root calculation. Removes values that are empty, + hashes the keys (if `secured == True`) and encodes all the nodes. + + Parameters + ---------- + trie : + The `Trie` to prepare. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + Returns + ------- + out : `Mapping[ethereum.base_types.Bytes, Node]` + Object with keys mapped to nibble-byte form. + """ + mapped: MutableMapping[Bytes, Bytes] = {} + + for preimage, value in trie._data.items(): + if isinstance(value, Account): + assert get_storage_root is not None + address = Address(preimage) + encoded_value = encode_node(value, get_storage_root(address)) + else: + encoded_value = encode_node(value) + if encoded_value == b"": + raise AssertionError + key: Bytes + if trie.secured: + # "secure" tries hash keys once before construction + key = keccak256(preimage) + else: + key = preimage + mapped[bytes_to_nibble_list(key)] = encoded_value + + return mapped + + +def root( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Root: + """ + Computes the root of a modified merkle patricia trie (MPT). + + Parameters + ---------- + trie : + `Trie` to get the root of. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + + Returns + ------- + root : `.fork_types.Root` + MPT root of the underlying key-value pairs. + """ + obj = _prepare_trie(trie, get_storage_root) + + root_node = encode_internal_node(patricialize(obj, Uint(0))) + if len(rlp.encode(root_node)) < 32: + return keccak256(rlp.encode(root_node)) + else: + assert isinstance(root_node, Bytes) + return Root(root_node) + + +def patricialize( + obj: Mapping[Bytes, Bytes], level: Uint +) -> Optional[InternalNode]: + """ + Structural composition function. + + Used to recursively patricialize and merkleize a dictionary. Includes + memoization of the tree structure and hashes. + + Parameters + ---------- + obj : + Underlying trie key-value pairs, with keys in nibble-list format. + level : + Current trie level. + + Returns + ------- + node : `ethereum.base_types.Bytes` + Root node of `obj`. + """ + if len(obj) == 0: + return None + + arbitrary_key = next(iter(obj)) + + # if leaf node + if len(obj) == 1: + leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) + return leaf + + # prepare for extension node check by finding max j such that all keys in + # obj have the same key[i:j] + substring = arbitrary_key[level:] + prefix_length = len(substring) + for key in obj: + prefix_length = min( + prefix_length, common_prefix_length(substring, key[level:]) + ) + + # finished searching, found another key at the current level + if prefix_length == 0: + break + + # if extension node + if prefix_length > 0: + prefix = arbitrary_key[int(level) : int(level) + prefix_length] + return ExtensionNode( + prefix, + encode_internal_node( + patricialize(obj, level + Uint(prefix_length)) + ), + ) + + branches: List[MutableMapping[Bytes, Bytes]] = [] + for _ in range(16): + branches.append({}) + value = b"" + for key in obj: + if len(key) == level: + # shouldn't ever have an account or receipt in an internal node + if isinstance(obj[key], (Account, Receipt, Uint)): + raise AssertionError + value = obj[key] + else: + branches[key[level]][key] = obj[key] + + subnodes = tuple( + encode_internal_node(patricialize(branches[k], level + Uint(1))) + for k in range(16) + ) + return BranchNode( + cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), + value, + ) diff --git a/src/ethereum/osaka/utils/__init__.py b/src/ethereum/osaka/utils/__init__.py new file mode 100644 index 0000000000..224a4d269b --- /dev/null +++ b/src/ethereum/osaka/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utility functions unique to this particular fork. +""" diff --git a/src/ethereum/osaka/utils/address.py b/src/ethereum/osaka/utils/address.py new file mode 100644 index 0000000000..1872dcf317 --- /dev/null +++ b/src/ethereum/osaka/utils/address.py @@ -0,0 +1,93 @@ +""" +Hardfork Utility Functions For Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Address specific functions used in this osaka version of +specification. +""" +from typing import Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.byte import left_pad_zero_bytes + +from ..fork_types import Address + + +def to_address(data: Union[Uint, U256]) -> Address: + """ + Convert a Uint or U256 value to a valid address (20 bytes). + + Parameters + ---------- + data : + The string to be converted to bytes. + + Returns + ------- + address : `Address` + The obtained address. + """ + return Address(data.to_be_bytes32()[-20:]) + + +def compute_contract_address(address: Address, nonce: Uint) -> Address: + """ + Computes address of the new account that needs to be created. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + nonce : + The transaction count of the account that wants to create the new + account. + + Returns + ------- + address: `Address` + The computed address of the new account. + """ + computed_address = keccak256(rlp.encode([address, nonce])) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + return Address(padded_address) + + +def compute_create2_contract_address( + address: Address, salt: Bytes32, call_data: bytearray +) -> Address: + """ + Computes address of the new account that needs to be created, which is + based on the sender address, salt and the call data as well. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + salt : + Address generation salt. + call_data : + The code of the new account which is to be created. + + Returns + ------- + address: `ethereum.osaka.fork_types.Address` + The computed address of the new account. + """ + preimage = b"\xff" + address + salt + keccak256(call_data) + computed_address = keccak256(preimage) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + + return Address(padded_address) diff --git a/src/ethereum/osaka/utils/hexadecimal.py b/src/ethereum/osaka/utils/hexadecimal.py new file mode 100644 index 0000000000..738ee85d4c --- /dev/null +++ b/src/ethereum/osaka/utils/hexadecimal.py @@ -0,0 +1,68 @@ +""" +Utility Functions For Hexadecimal Strings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Hexadecimal utility functions used in this specification, specific to +Osaka types. +""" +from ethereum.utils.hexadecimal import remove_hex_prefix + +from ..fork_types import Address, Bloom, Root + + +def hex_to_root(hex_string: str) -> Root: + """ + Convert hex string to trie root. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to trie root. + + Returns + ------- + root : `Root` + Trie root obtained from the given hexadecimal string. + """ + return Root(bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_bloom(hex_string: str) -> Bloom: + """ + Convert hex string to bloom. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to bloom. + + Returns + ------- + bloom : `Bloom` + Bloom obtained from the given hexadecimal string. + """ + return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_address(hex_string: str) -> Address: + """ + Convert hex string to Address (20 bytes). + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to Address. + + Returns + ------- + address : `Address` + The address obtained from the given hexadecimal string. + """ + return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py new file mode 100644 index 0000000000..eac49d7a2f --- /dev/null +++ b/src/ethereum/osaka/utils/message.py @@ -0,0 +1,95 @@ +""" +Hardfork Utility Functions For The Message Data-structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Message specific functions used in this osaka version of +specification. +""" +from ethereum_types.bytes import Bytes, Bytes0 +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.eoa_delegation import get_delegated_code_address +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from .address import compute_contract_address + + +def prepare_message( + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, +) -> Message: + """ + Execute a transaction against the provided environment. + + Parameters + ---------- + block_env : + Environment for the Ethereum Virtual Machine. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. + + Returns + ------- + message: `ethereum.osaka.vm.Message` + Items containing contract creation or message call specific data. + """ + accessed_addresses = set() + accessed_addresses.add(tx_env.origin) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(tx_env.access_list_addresses) + + if isinstance(tx.to, Bytes0): + current_target = compute_contract_address( + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), + ) + msg_data = Bytes(b"") + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + delegated_address = get_delegated_code_address(code) + if delegated_address is not None: + accessed_addresses.add(delegated_address) + code = get_account(block_env.state, delegated_address).code + + code_address = tx.to + else: + raise AssertionError("Target must be address or empty bytes") + + accessed_addresses.add(current_target) + + return Message( + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, + data=msg_data, + code=code, + depth=Uint(0), + current_target=current_target, + code_address=code_address, + should_transfer_value=True, + is_static=False, + accessed_addresses=accessed_addresses, + accessed_storage_keys=set(tx_env.access_list_storage_keys), + parent_evm=None, + ) diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py new file mode 100644 index 0000000000..55f1b92499 --- /dev/null +++ b/src/ethereum/osaka/vm/__init__.py @@ -0,0 +1,209 @@ +""" +Ethereum Virtual Machine (EVM) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The abstract computer which runs the code stored in an +`.fork_types.Account`. +""" + +from dataclasses import dataclass, field +from typing import List, Optional, Set, Tuple, Union + +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 ..blocks import Log, Receipt, Withdrawal +from ..fork_types import Address, Authorization, VersionedHash +from ..state import State, TransientStorage, account_exists_and_is_empty +from ..transactions import LegacyTransaction +from ..trie import Trie +from .precompiled_contracts import RIPEMD160_ADDRESS + +__all__ = ("Environment", "Evm", "Message") + + +@dataclass +class BlockEnvironment: + """ + Items external to the virtual machine itself, provided by the environment. + """ + + chain_id: U64 + state: State + block_gas_limit: Uint + block_hashes: List[Hash32] + coinbase: Address + number: Uint + base_fee_per_gas: Uint + time: U256 + prev_randao: Bytes32 + excess_blob_gas: U64 + parent_beacon_block_root: Hash32 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + withdrawals_trie : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + blob_gas_used : `ethereum.base_types.Uint` + Total blob gas used in the block. + requests : `Bytes` + Hash of all the requests in the block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + blob_gas_used: Uint = Uint(0) + deposit_requests: Bytes = Bytes(b"") + requests: List[Bytes] = field(default_factory=list) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + transient_storage: TransientStorage + blob_versioned_hashes: Tuple[VersionedHash, ...] + authorizations: Tuple[Authorization, ...] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] + traces: List[dict] + + +@dataclass +class Message: + """ + Items that are used by contract creation or message call. + """ + + block_env: BlockEnvironment + tx_env: TransactionEnvironment + caller: Address + target: Union[Bytes0, Address] + current_target: Address + gas: Uint + value: U256 + data: Bytes + code_address: Optional[Address] + code: Bytes + depth: Uint + should_transfer_value: bool + is_static: bool + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + parent_evm: Optional["Evm"] + + +@dataclass +class Evm: + """The internal state of the virtual machine.""" + + pc: Uint + stack: List[U256] + memory: bytearray + code: Bytes + gas_left: Uint + valid_jump_destinations: Set[Uint] + logs: Tuple[Log, ...] + refund_counter: int + running: bool + message: Message + output: Bytes + accounts_to_delete: Set[Address] + touched_accounts: Set[Address] + return_data: Bytes + error: Optional[EthereumException] + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + + +def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of a successful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + evm.gas_left += child_evm.gas_left + evm.logs += child_evm.logs + evm.refund_counter += child_evm.refund_counter + evm.accounts_to_delete.update(child_evm.accounts_to_delete) + evm.touched_accounts.update(child_evm.touched_accounts) + if account_exists_and_is_empty( + evm.message.block_env.state, child_evm.message.current_target + ): + evm.touched_accounts.add(child_evm.message.current_target) + evm.accessed_addresses.update(child_evm.accessed_addresses) + evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) + + +def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was + # cleared despite running out of gas. This is an obscure edge case that can + # only happen to a precompile. + # According to the general rules governing clearing of empty accounts, the + # touch should have been reverted. Due to client bugs, this event went + # unnoticed and 0x3 has been exempted from the rule that touches are + # reverted in order to preserve this historical behaviour. + if RIPEMD160_ADDRESS in child_evm.touched_accounts: + evm.touched_accounts.add(RIPEMD160_ADDRESS) + if child_evm.message.current_target == RIPEMD160_ADDRESS: + if account_exists_and_is_empty( + evm.message.block_env.state, child_evm.message.current_target + ): + evm.touched_accounts.add(RIPEMD160_ADDRESS) + evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py new file mode 100644 index 0000000000..bb6a7e33d9 --- /dev/null +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -0,0 +1,214 @@ +""" +Set EOA account code. +""" + + +from typing import Optional, Tuple + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import keccak256 +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 + +SET_CODE_TX_MAGIC = b"\x05" +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 +PER_AUTH_BASE_COST = 12500 +NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") + + +def is_valid_delegation(code: bytes) -> bool: + """ + Whether the code is a valid delegation designation. + + Parameters + ---------- + code: `bytes` + The code to check. + + Returns + ------- + valid : `bool` + True if the code is a valid delegation designation, + False otherwise. + """ + if ( + len(code) == EOA_DELEGATED_CODE_LENGTH + and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER + ): + return True + return False + + +def get_delegated_code_address(code: bytes) -> Optional[Address]: + """ + Get the address to which the code delegates. + + Parameters + ---------- + code: `bytes` + The code to get the address from. + + Returns + ------- + address : `Optional[Address]` + The address of the delegated code. + """ + if is_valid_delegation(code): + return Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + return None + + +def recover_authority(authorization: Authorization) -> Address: + """ + Recover the authority address from the authorization. + + Parameters + ---------- + authorization + The authorization to recover the authority from. + + Raises + ------ + InvalidSignatureError + If the signature is invalid. + + Returns + ------- + authority : `Address` + The recovered authority address. + """ + y_parity, r, s = authorization.y_parity, authorization.r, authorization.s + if y_parity not in (0, 1): + raise InvalidSignatureError("Invalid y_parity in authorization") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("Invalid r value in authorization") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("Invalid s value in authorization") + + signing_hash = keccak256( + SET_CODE_TX_MAGIC + + rlp.encode( + ( + authorization.chain_id, + authorization.address, + authorization.nonce, + ) + ) + ) + + public_key = secp256k1_recover(r, s, U256(y_parity), signing_hash) + return Address(keccak256(public_key)[12:32]) + + +def access_delegation( + evm: Evm, address: Address +) -> Tuple[bool, Address, Bytes, Uint]: + """ + Get the delegation address, code, and the cost of access from the address. + + Parameters + ---------- + evm : `Evm` + The execution frame. + address : `Address` + The address to get the delegation from. + + Returns + ------- + 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 + if not is_valid_delegation(code): + return False, address, code, Uint(0) + + address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code = get_account(state, address).code + + return True, address, code, access_gas_cost + + +def set_delegation(message: Message) -> U256: + """ + Set the delegation code for the authorities in the message. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + refund_counter: `U256` + Refund from authority which already exists in state. + """ + state = message.block_env.state + refund_counter = U256(0) + for auth in message.tx_env.authorizations: + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + continue + + if auth.nonce >= U64.MAX_VALUE: + continue + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + continue + + message.accessed_addresses.add(authority) + + authority_account = get_account(state, authority) + authority_code = authority_account.code + + if authority_code and not is_valid_delegation(authority_code): + continue + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + continue + + if account_exists(state, 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) + + increment_nonce(state, authority) + + if message.code_address is None: + raise InvalidBlock("Invalid type 4 transaction: no target") + message.code = get_account(state, message.code_address).code + + if is_valid_delegation(message.code): + message.code_address = Address( + message.code[EOA_DELEGATION_MARKER_LENGTH:] + ) + message.accessed_addresses.add(message.code_address) + + message.code = get_account(state, message.code_address).code + + return refund_counter diff --git a/src/ethereum/osaka/vm/exceptions.py b/src/ethereum/osaka/vm/exceptions.py new file mode 100644 index 0000000000..2a4f2d2f65 --- /dev/null +++ b/src/ethereum/osaka/vm/exceptions.py @@ -0,0 +1,140 @@ +""" +Ethereum Virtual Machine (EVM) Exceptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Exceptions which cause the EVM to halt exceptionally. +""" + +from ethereum.exceptions import EthereumException + + +class ExceptionalHalt(EthereumException): + """ + Indicates that the EVM has experienced an exceptional halt. This causes + execution to immediately end with all gas being consumed. + """ + + +class Revert(EthereumException): + """ + Raised by the `REVERT` opcode. + + Unlike other EVM exceptions this does not result in the consumption of all + gas. + """ + + pass + + +class StackUnderflowError(ExceptionalHalt): + """ + Occurs when a pop is executed on an empty stack. + """ + + pass + + +class StackOverflowError(ExceptionalHalt): + """ + Occurs when a push is executed on a stack at max capacity. + """ + + pass + + +class OutOfGasError(ExceptionalHalt): + """ + Occurs when an operation costs more than the amount of gas left in the + frame. + """ + + pass + + +class InvalidOpcode(ExceptionalHalt): + """ + Raised when an invalid opcode is encountered. + """ + + code: int + + def __init__(self, code: int) -> None: + super().__init__(code) + self.code = code + + +class InvalidJumpDestError(ExceptionalHalt): + """ + Occurs when the destination of a jump operation doesn't meet any of the + following criteria: + + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + """ + + +class StackDepthLimitError(ExceptionalHalt): + """ + Raised when the message depth is greater than `1024` + """ + + pass + + +class WriteInStaticContext(ExceptionalHalt): + """ + Raised when an attempt is made to modify the state while operating inside + of a STATICCALL context. + """ + + pass + + +class OutOfBoundsRead(ExceptionalHalt): + """ + Raised when an attempt was made to read data beyond the + boundaries of the buffer. + """ + + pass + + +class InvalidParameter(ExceptionalHalt): + """ + Raised when invalid parameters are passed. + """ + + pass + + +class InvalidContractPrefix(ExceptionalHalt): + """ + Raised when the new contract code starts with 0xEF. + """ + + pass + + +class AddressCollision(ExceptionalHalt): + """ + Raised when the new contract address has a collision. + """ + + pass + + +class KZGProofError(ExceptionalHalt): + """ + Raised when the point evaluation precompile can't verify a proof. + """ + + pass diff --git a/src/ethereum/osaka/vm/gas.py b/src/ethereum/osaka/vm/gas.py new file mode 100644 index 0000000000..624a8f86e2 --- /dev/null +++ b/src/ethereum/osaka/vm/gas.py @@ -0,0 +1,369 @@ +""" +Ethereum Virtual Machine (EVM) Gas +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM gas constants and calculators. +""" +from dataclasses import dataclass +from typing import List, Tuple + +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.trace import GasAndRefund, evm_trace +from ethereum.utils.numeric import ceil32, taylor_exponential + +from ..blocks import Header +from ..transactions import BlobTransaction, Transaction +from . import Evm +from .exceptions import OutOfGasError + +GAS_JUMPDEST = Uint(1) +GAS_BASE = Uint(2) +GAS_VERY_LOW = Uint(3) +GAS_STORAGE_SET = Uint(20000) +GAS_STORAGE_UPDATE = Uint(5000) +GAS_STORAGE_CLEAR_REFUND = Uint(4800) +GAS_LOW = Uint(5) +GAS_MID = Uint(8) +GAS_HIGH = Uint(10) +GAS_EXPONENTIATION = Uint(10) +GAS_EXPONENTIATION_PER_BYTE = Uint(50) +GAS_MEMORY = Uint(3) +GAS_KECCAK256 = Uint(30) +GAS_KECCAK256_WORD = Uint(6) +GAS_COPY = Uint(3) +GAS_BLOCK_HASH = Uint(20) +GAS_LOG = Uint(375) +GAS_LOG_DATA = Uint(8) +GAS_LOG_TOPIC = Uint(375) +GAS_CREATE = Uint(32000) +GAS_CODE_DEPOSIT = Uint(200) +GAS_ZERO = Uint(0) +GAS_NEW_ACCOUNT = Uint(25000) +GAS_CALL_VALUE = Uint(9000) +GAS_CALL_STIPEND = Uint(2300) +GAS_SELF_DESTRUCT = Uint(5000) +GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000) +GAS_ECRECOVER = Uint(3000) +GAS_SHA256 = Uint(60) +GAS_SHA256_WORD = Uint(12) +GAS_RIPEMD160 = Uint(600) +GAS_RIPEMD160_WORD = Uint(120) +GAS_IDENTITY = Uint(15) +GAS_IDENTITY_WORD = Uint(3) +GAS_RETURN_DATA_COPY = Uint(3) +GAS_FAST_STEP = Uint(5) +GAS_BLAKE2_PER_ROUND = Uint(1) +GAS_COLD_SLOAD = Uint(2100) +GAS_COLD_ACCOUNT_ACCESS = Uint(2600) +GAS_WARM_ACCESS = Uint(100) +GAS_INIT_CODE_WORD_COST = Uint(2) +GAS_BLOBHASH_OPCODE = Uint(3) +GAS_POINT_EVALUATION = Uint(50000) + +TARGET_BLOB_GAS_PER_BLOCK = U64(786432) +GAS_PER_BLOB = Uint(2**17) +MIN_BLOB_GASPRICE = Uint(1) +BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716) + +GAS_BLS_G1_ADD = Uint(375) +GAS_BLS_G1_MUL = Uint(12000) +GAS_BLS_G1_MAP = Uint(5500) +GAS_BLS_G2_ADD = Uint(600) +GAS_BLS_G2_MUL = Uint(22500) +GAS_BLS_G2_MAP = Uint(23800) + + +@dataclass +class ExtendMemory: + """ + Define the parameters for memory extension in opcodes + + `cost`: `ethereum.base_types.Uint` + The gas required to perform the extension + `expand_by`: `ethereum.base_types.Uint` + The size by which the memory will be extended + """ + + cost: Uint + expand_by: Uint + + +@dataclass +class MessageCallGas: + """ + Define the gas cost and stipend for executing the call opcodes. + + `cost`: `ethereum.base_types.Uint` + The non-refundable portion of gas reserved for executing the + call opcode. + `stipend`: `ethereum.base_types.Uint` + The portion of gas available to sub-calls that is refundable + if not consumed + """ + + cost: Uint + stipend: Uint + + +def charge_gas(evm: Evm, amount: Uint) -> None: + """ + Subtracts `amount` from `evm.gas_left`. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas the current operation requires. + + """ + evm_trace(evm, GasAndRefund(int(amount))) + + if evm.gas_left < amount: + raise OutOfGasError + else: + evm.gas_left -= amount + + +def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: + """ + Calculates the gas cost for allocating memory + to the smallest multiple of 32 bytes, + such that the allocated size is at least as big as the given size. + + Parameters + ---------- + size_in_bytes : + The size of the data in bytes. + + Returns + ------- + total_gas_cost : `ethereum.base_types.Uint` + The gas cost for storing data in memory. + """ + size_in_words = ceil32(size_in_bytes) // Uint(32) + linear_cost = size_in_words * GAS_MEMORY + quadratic_cost = size_in_words ** Uint(2) // Uint(512) + total_gas_cost = linear_cost + quadratic_cost + try: + return total_gas_cost + except ValueError: + raise OutOfGasError + + +def calculate_gas_extend_memory( + memory: bytearray, extensions: List[Tuple[U256, U256]] +) -> ExtendMemory: + """ + Calculates the gas amount to extend memory + + Parameters + ---------- + memory : + Memory contents of the EVM. + extensions: + List of extensions to be made to the memory. + Consists of a tuple of start position and size. + + Returns + ------- + extend_memory: `ExtendMemory` + """ + size_to_extend = Uint(0) + to_be_paid = Uint(0) + current_size = Uint(len(memory)) + for start_position, size in extensions: + if size == 0: + continue + before_size = ceil32(current_size) + after_size = ceil32(Uint(start_position) + Uint(size)) + if after_size <= before_size: + continue + + size_to_extend += after_size - before_size + already_paid = calculate_memory_gas_cost(before_size) + total_cost = calculate_memory_gas_cost(after_size) + to_be_paid += total_cost - already_paid + + current_size = after_size + + return ExtendMemory(to_be_paid, size_to_extend) + + +def calculate_message_call_gas( + value: U256, + gas: Uint, + gas_left: Uint, + memory_cost: Uint, + extra_gas: Uint, + call_stipend: Uint = GAS_CALL_STIPEND, +) -> MessageCallGas: + """ + Calculates the MessageCallGas (cost and stipend) for + executing call Opcodes. + + Parameters + ---------- + value: + The amount of `ETH` that needs to be transferred. + gas : + The amount of gas provided to the message-call. + gas_left : + The amount of gas left in the current frame. + memory_cost : + The amount needed to extend the memory in the current frame. + extra_gas : + The amount of gas needed for transferring value + creating a new + account inside a message call. + call_stipend : + The amount of stipend provided to a message call to execute code while + transferring value(ETH). + + Returns + ------- + message_call_gas: `MessageCallGas` + """ + call_stipend = Uint(0) if value == 0 else call_stipend + if gas_left < extra_gas + memory_cost: + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + gas = min(gas, max_message_call_gas(gas_left - memory_cost - extra_gas)) + + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + +def max_message_call_gas(gas: Uint) -> Uint: + """ + Calculates the maximum gas that is allowed for making a message call + + Parameters + ---------- + gas : + The amount of gas provided to the message-call. + + Returns + ------- + max_allowed_message_call_gas: `ethereum.base_types.Uint` + The maximum gas allowed for making the message-call. + """ + return gas - (gas // Uint(64)) + + +def init_code_cost(init_code_length: Uint) -> Uint: + """ + Calculates the gas to be charged for the init code in CREAT* + opcodes as well as create transactions. + + Parameters + ---------- + init_code_length : + The length of the init code provided to the opcode + or a create transaction + + Returns + ------- + init_code_gas: `ethereum.base_types.Uint` + The gas to be charged for the init code. + """ + return GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) // Uint(32) + + +def calculate_excess_blob_gas(parent_header: Header) -> U64: + """ + Calculated the excess blob gas for the current block based + on the gas used in the parent block. + + Parameters + ---------- + parent_header : + The parent block of the current block. + + Returns + ------- + excess_blob_gas: `ethereum.base_types.U64` + The excess blob gas for the current block. + """ + # At the fork block, these are defined as zero. + excess_blob_gas = U64(0) + blob_gas_used = U64(0) + + if isinstance(parent_header, Header): + # After the fork block, read them from the parent header. + excess_blob_gas = parent_header.excess_blob_gas + blob_gas_used = parent_header.blob_gas_used + + parent_blob_gas = excess_blob_gas + blob_gas_used + if parent_blob_gas < TARGET_BLOB_GAS_PER_BLOCK: + return U64(0) + else: + return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK + + +def calculate_total_blob_gas(tx: Transaction) -> Uint: + """ + Calculate the total blob gas for a transaction. + + Parameters + ---------- + tx : + The transaction for which the blob gas is to be calculated. + + Returns + ------- + total_blob_gas: `ethereum.base_types.Uint` + The total blob gas for the transaction. + """ + if isinstance(tx, BlobTransaction): + return GAS_PER_BLOB * Uint(len(tx.blob_versioned_hashes)) + else: + return Uint(0) + + +def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: + """ + Calculate the blob gasprice for a block. + + Parameters + ---------- + excess_blob_gas : + The excess blob gas for the block. + + Returns + ------- + blob_gasprice: `Uint` + The blob gasprice. + """ + return taylor_exponential( + MIN_BLOB_GASPRICE, + Uint(excess_blob_gas), + BLOB_BASE_FEE_UPDATE_FRACTION, + ) + + +def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: + """ + Calculate the blob data fee for a transaction. + + Parameters + ---------- + excess_blob_gas : + The excess_blob_gas for the execution. + tx : + The transaction for which the blob data fee is to be calculated. + + Returns + ------- + data_fee: `Uint` + The blob data fee. + """ + return calculate_total_blob_gas(tx) * calculate_blob_gas_price( + excess_blob_gas + ) diff --git a/src/ethereum/osaka/vm/instructions/__init__.py b/src/ethereum/osaka/vm/instructions/__init__.py new file mode 100644 index 0000000000..b220581c72 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/__init__.py @@ -0,0 +1,366 @@ +""" +EVM Instruction Encoding (Opcodes) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Machine readable representations of EVM instructions, and a mapping to their +implementations. +""" + +import enum +from typing import Callable, Dict + +from . import arithmetic as arithmetic_instructions +from . import bitwise as bitwise_instructions +from . import block as block_instructions +from . import comparison as comparison_instructions +from . import control_flow as control_flow_instructions +from . import environment as environment_instructions +from . import keccak as keccak_instructions +from . import log as log_instructions +from . import memory as memory_instructions +from . import stack as stack_instructions +from . import storage as storage_instructions +from . import system as system_instructions + + +class Ops(enum.Enum): + """ + Enum for EVM Opcodes + """ + + # Arithmetic Ops + ADD = 0x01 + MUL = 0x02 + SUB = 0x03 + DIV = 0x04 + SDIV = 0x05 + MOD = 0x06 + SMOD = 0x07 + ADDMOD = 0x08 + MULMOD = 0x09 + EXP = 0x0A + SIGNEXTEND = 0x0B + + # Comparison Ops + LT = 0x10 + GT = 0x11 + SLT = 0x12 + SGT = 0x13 + EQ = 0x14 + ISZERO = 0x15 + + # Bitwise Ops + AND = 0x16 + OR = 0x17 + XOR = 0x18 + NOT = 0x19 + BYTE = 0x1A + SHL = 0x1B + SHR = 0x1C + SAR = 0x1D + + # Keccak Op + KECCAK = 0x20 + + # Environmental Ops + ADDRESS = 0x30 + BALANCE = 0x31 + ORIGIN = 0x32 + CALLER = 0x33 + CALLVALUE = 0x34 + CALLDATALOAD = 0x35 + CALLDATASIZE = 0x36 + CALLDATACOPY = 0x37 + CODESIZE = 0x38 + CODECOPY = 0x39 + GASPRICE = 0x3A + EXTCODESIZE = 0x3B + EXTCODECOPY = 0x3C + RETURNDATASIZE = 0x3D + RETURNDATACOPY = 0x3E + EXTCODEHASH = 0x3F + + # Block Ops + BLOCKHASH = 0x40 + COINBASE = 0x41 + TIMESTAMP = 0x42 + NUMBER = 0x43 + PREVRANDAO = 0x44 + GASLIMIT = 0x45 + CHAINID = 0x46 + SELFBALANCE = 0x47 + BASEFEE = 0x48 + BLOBHASH = 0x49 + BLOBBASEFEE = 0x4A + + # Control Flow Ops + STOP = 0x00 + JUMP = 0x56 + JUMPI = 0x57 + PC = 0x58 + GAS = 0x5A + JUMPDEST = 0x5B + + # Storage Ops + SLOAD = 0x54 + SSTORE = 0x55 + TLOAD = 0x5C + TSTORE = 0x5D + + # Pop Operation + POP = 0x50 + + # Push Operations + PUSH0 = 0x5F + PUSH1 = 0x60 + PUSH2 = 0x61 + PUSH3 = 0x62 + PUSH4 = 0x63 + PUSH5 = 0x64 + PUSH6 = 0x65 + PUSH7 = 0x66 + PUSH8 = 0x67 + PUSH9 = 0x68 + PUSH10 = 0x69 + PUSH11 = 0x6A + PUSH12 = 0x6B + PUSH13 = 0x6C + PUSH14 = 0x6D + PUSH15 = 0x6E + PUSH16 = 0x6F + PUSH17 = 0x70 + PUSH18 = 0x71 + PUSH19 = 0x72 + PUSH20 = 0x73 + PUSH21 = 0x74 + PUSH22 = 0x75 + PUSH23 = 0x76 + PUSH24 = 0x77 + PUSH25 = 0x78 + PUSH26 = 0x79 + PUSH27 = 0x7A + PUSH28 = 0x7B + PUSH29 = 0x7C + PUSH30 = 0x7D + PUSH31 = 0x7E + PUSH32 = 0x7F + + # Dup operations + DUP1 = 0x80 + DUP2 = 0x81 + DUP3 = 0x82 + DUP4 = 0x83 + DUP5 = 0x84 + DUP6 = 0x85 + DUP7 = 0x86 + DUP8 = 0x87 + DUP9 = 0x88 + DUP10 = 0x89 + DUP11 = 0x8A + DUP12 = 0x8B + DUP13 = 0x8C + DUP14 = 0x8D + DUP15 = 0x8E + DUP16 = 0x8F + + # Swap operations + SWAP1 = 0x90 + SWAP2 = 0x91 + SWAP3 = 0x92 + SWAP4 = 0x93 + SWAP5 = 0x94 + SWAP6 = 0x95 + SWAP7 = 0x96 + SWAP8 = 0x97 + SWAP9 = 0x98 + SWAP10 = 0x99 + SWAP11 = 0x9A + SWAP12 = 0x9B + SWAP13 = 0x9C + SWAP14 = 0x9D + SWAP15 = 0x9E + SWAP16 = 0x9F + + # Memory Operations + MLOAD = 0x51 + MSTORE = 0x52 + MSTORE8 = 0x53 + MSIZE = 0x59 + MCOPY = 0x5E + + # Log Operations + LOG0 = 0xA0 + LOG1 = 0xA1 + LOG2 = 0xA2 + LOG3 = 0xA3 + LOG4 = 0xA4 + + # System Operations + CREATE = 0xF0 + CALL = 0xF1 + CALLCODE = 0xF2 + RETURN = 0xF3 + DELEGATECALL = 0xF4 + CREATE2 = 0xF5 + STATICCALL = 0xFA + REVERT = 0xFD + SELFDESTRUCT = 0xFF + + +op_implementation: Dict[Ops, Callable] = { + Ops.STOP: control_flow_instructions.stop, + Ops.ADD: arithmetic_instructions.add, + Ops.MUL: arithmetic_instructions.mul, + Ops.SUB: arithmetic_instructions.sub, + Ops.DIV: arithmetic_instructions.div, + Ops.SDIV: arithmetic_instructions.sdiv, + Ops.MOD: arithmetic_instructions.mod, + Ops.SMOD: arithmetic_instructions.smod, + Ops.ADDMOD: arithmetic_instructions.addmod, + Ops.MULMOD: arithmetic_instructions.mulmod, + Ops.EXP: arithmetic_instructions.exp, + Ops.SIGNEXTEND: arithmetic_instructions.signextend, + Ops.LT: comparison_instructions.less_than, + Ops.GT: comparison_instructions.greater_than, + Ops.SLT: comparison_instructions.signed_less_than, + Ops.SGT: comparison_instructions.signed_greater_than, + Ops.EQ: comparison_instructions.equal, + Ops.ISZERO: comparison_instructions.is_zero, + Ops.AND: bitwise_instructions.bitwise_and, + Ops.OR: bitwise_instructions.bitwise_or, + Ops.XOR: bitwise_instructions.bitwise_xor, + Ops.NOT: bitwise_instructions.bitwise_not, + Ops.BYTE: bitwise_instructions.get_byte, + Ops.SHL: bitwise_instructions.bitwise_shl, + Ops.SHR: bitwise_instructions.bitwise_shr, + Ops.SAR: bitwise_instructions.bitwise_sar, + Ops.KECCAK: keccak_instructions.keccak, + Ops.SLOAD: storage_instructions.sload, + Ops.BLOCKHASH: block_instructions.block_hash, + Ops.COINBASE: block_instructions.coinbase, + Ops.TIMESTAMP: block_instructions.timestamp, + Ops.NUMBER: block_instructions.number, + Ops.PREVRANDAO: block_instructions.prev_randao, + Ops.GASLIMIT: block_instructions.gas_limit, + Ops.CHAINID: block_instructions.chain_id, + Ops.MLOAD: memory_instructions.mload, + Ops.MSTORE: memory_instructions.mstore, + Ops.MSTORE8: memory_instructions.mstore8, + Ops.MSIZE: memory_instructions.msize, + Ops.MCOPY: memory_instructions.mcopy, + Ops.ADDRESS: environment_instructions.address, + Ops.BALANCE: environment_instructions.balance, + Ops.ORIGIN: environment_instructions.origin, + Ops.CALLER: environment_instructions.caller, + Ops.CALLVALUE: environment_instructions.callvalue, + Ops.CALLDATALOAD: environment_instructions.calldataload, + Ops.CALLDATASIZE: environment_instructions.calldatasize, + Ops.CALLDATACOPY: environment_instructions.calldatacopy, + Ops.CODESIZE: environment_instructions.codesize, + Ops.CODECOPY: environment_instructions.codecopy, + Ops.GASPRICE: environment_instructions.gasprice, + Ops.EXTCODESIZE: environment_instructions.extcodesize, + Ops.EXTCODECOPY: environment_instructions.extcodecopy, + Ops.RETURNDATASIZE: environment_instructions.returndatasize, + Ops.RETURNDATACOPY: environment_instructions.returndatacopy, + Ops.EXTCODEHASH: environment_instructions.extcodehash, + Ops.SELFBALANCE: environment_instructions.self_balance, + Ops.BASEFEE: environment_instructions.base_fee, + Ops.BLOBHASH: environment_instructions.blob_hash, + Ops.BLOBBASEFEE: environment_instructions.blob_base_fee, + Ops.SSTORE: storage_instructions.sstore, + Ops.TLOAD: storage_instructions.tload, + Ops.TSTORE: storage_instructions.tstore, + Ops.JUMP: control_flow_instructions.jump, + Ops.JUMPI: control_flow_instructions.jumpi, + Ops.PC: control_flow_instructions.pc, + Ops.GAS: control_flow_instructions.gas_left, + Ops.JUMPDEST: control_flow_instructions.jumpdest, + Ops.POP: stack_instructions.pop, + Ops.PUSH0: stack_instructions.push0, + Ops.PUSH1: stack_instructions.push1, + Ops.PUSH2: stack_instructions.push2, + Ops.PUSH3: stack_instructions.push3, + Ops.PUSH4: stack_instructions.push4, + Ops.PUSH5: stack_instructions.push5, + Ops.PUSH6: stack_instructions.push6, + Ops.PUSH7: stack_instructions.push7, + Ops.PUSH8: stack_instructions.push8, + Ops.PUSH9: stack_instructions.push9, + Ops.PUSH10: stack_instructions.push10, + Ops.PUSH11: stack_instructions.push11, + Ops.PUSH12: stack_instructions.push12, + Ops.PUSH13: stack_instructions.push13, + Ops.PUSH14: stack_instructions.push14, + Ops.PUSH15: stack_instructions.push15, + Ops.PUSH16: stack_instructions.push16, + Ops.PUSH17: stack_instructions.push17, + Ops.PUSH18: stack_instructions.push18, + Ops.PUSH19: stack_instructions.push19, + Ops.PUSH20: stack_instructions.push20, + Ops.PUSH21: stack_instructions.push21, + Ops.PUSH22: stack_instructions.push22, + Ops.PUSH23: stack_instructions.push23, + Ops.PUSH24: stack_instructions.push24, + Ops.PUSH25: stack_instructions.push25, + Ops.PUSH26: stack_instructions.push26, + Ops.PUSH27: stack_instructions.push27, + Ops.PUSH28: stack_instructions.push28, + Ops.PUSH29: stack_instructions.push29, + Ops.PUSH30: stack_instructions.push30, + Ops.PUSH31: stack_instructions.push31, + Ops.PUSH32: stack_instructions.push32, + Ops.DUP1: stack_instructions.dup1, + Ops.DUP2: stack_instructions.dup2, + Ops.DUP3: stack_instructions.dup3, + Ops.DUP4: stack_instructions.dup4, + Ops.DUP5: stack_instructions.dup5, + Ops.DUP6: stack_instructions.dup6, + Ops.DUP7: stack_instructions.dup7, + Ops.DUP8: stack_instructions.dup8, + Ops.DUP9: stack_instructions.dup9, + Ops.DUP10: stack_instructions.dup10, + Ops.DUP11: stack_instructions.dup11, + Ops.DUP12: stack_instructions.dup12, + Ops.DUP13: stack_instructions.dup13, + Ops.DUP14: stack_instructions.dup14, + Ops.DUP15: stack_instructions.dup15, + Ops.DUP16: stack_instructions.dup16, + Ops.SWAP1: stack_instructions.swap1, + Ops.SWAP2: stack_instructions.swap2, + Ops.SWAP3: stack_instructions.swap3, + Ops.SWAP4: stack_instructions.swap4, + Ops.SWAP5: stack_instructions.swap5, + Ops.SWAP6: stack_instructions.swap6, + Ops.SWAP7: stack_instructions.swap7, + Ops.SWAP8: stack_instructions.swap8, + Ops.SWAP9: stack_instructions.swap9, + Ops.SWAP10: stack_instructions.swap10, + Ops.SWAP11: stack_instructions.swap11, + Ops.SWAP12: stack_instructions.swap12, + Ops.SWAP13: stack_instructions.swap13, + Ops.SWAP14: stack_instructions.swap14, + Ops.SWAP15: stack_instructions.swap15, + Ops.SWAP16: stack_instructions.swap16, + Ops.LOG0: log_instructions.log0, + Ops.LOG1: log_instructions.log1, + Ops.LOG2: log_instructions.log2, + Ops.LOG3: log_instructions.log3, + Ops.LOG4: log_instructions.log4, + Ops.CREATE: system_instructions.create, + Ops.RETURN: system_instructions.return_, + Ops.CALL: system_instructions.call, + Ops.CALLCODE: system_instructions.callcode, + Ops.DELEGATECALL: system_instructions.delegatecall, + Ops.SELFDESTRUCT: system_instructions.selfdestruct, + Ops.STATICCALL: system_instructions.staticcall, + Ops.REVERT: system_instructions.revert, + Ops.CREATE2: system_instructions.create2, +} diff --git a/src/ethereum/osaka/vm/instructions/arithmetic.py b/src/ethereum/osaka/vm/instructions/arithmetic.py new file mode 100644 index 0000000000..0b8df99543 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/arithmetic.py @@ -0,0 +1,373 @@ +""" +Ethereum Virtual Machine (EVM) Arithmetic Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Arithmetic instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import get_sign + +from .. import Evm +from ..gas import ( + GAS_EXPONENTIATION, + GAS_EXPONENTIATION_PER_BYTE, + GAS_LOW, + GAS_MID, + GAS_VERY_LOW, + charge_gas, +) +from ..stack import pop, push + + +def add(evm: Evm) -> None: + """ + Adds the top two elements of the stack together, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_add(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sub(evm: Evm) -> None: + """ + Subtracts the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_sub(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mul(evm: Evm) -> None: + """ + Multiply the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + result = x.wrapping_mul(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def div(evm: Evm) -> None: + """ + Integer division of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack) + divisor = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = U256(0) + else: + quotient = dividend // divisor + + push(evm.stack, quotient) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +U255_CEIL_VALUE = 2**255 + + +def sdiv(evm: Evm) -> None: + """ + Signed integer division of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack).to_signed() + divisor = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = 0 + elif dividend == -U255_CEIL_VALUE and divisor == -1: + quotient = -U255_CEIL_VALUE + else: + sign = get_sign(dividend * divisor) + quotient = sign * (abs(dividend) // abs(divisor)) + + push(evm.stack, U256.from_signed(quotient)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mod(evm: Evm) -> None: + """ + Modulo remainder of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = U256(0) + else: + remainder = x % y + + push(evm.stack, remainder) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def smod(evm: Evm) -> None: + """ + Signed modulo remainder of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack).to_signed() + y = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = 0 + else: + remainder = get_sign(x) * (abs(x) % abs(y)) + + push(evm.stack, U256.from_signed(remainder)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def addmod(evm: Evm) -> None: + """ + Modulo addition of the top 2 elements with the 3rd element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x + y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mulmod(evm: Evm) -> None: + """ + Modulo multiplication of the top 2 elements with the 3rd element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x * y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def exp(evm: Evm) -> None: + """ + Exponential operation of the top 2 elements. Pushes the result back on + the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + base = Uint(pop(evm.stack)) + exponent = Uint(pop(evm.stack)) + + # GAS + # This is equivalent to 1 + floor(log(y, 256)). But in python the log + # function is inaccurate leading to wrong results. + exponent_bits = exponent.bit_length() + exponent_bytes = (exponent_bits + Uint(7)) // Uint(8) + charge_gas( + evm, GAS_EXPONENTIATION + GAS_EXPONENTIATION_PER_BYTE * exponent_bytes + ) + + # OPERATION + result = U256(pow(base, exponent, Uint(U256.MAX_VALUE) + Uint(1))) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signextend(evm: Evm) -> None: + """ + Sign extend operation. In other words, extend a signed number which + fits in N bytes to 32 bytes. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_num = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if byte_num > U256(31): + # Can't extend any further + result = value + else: + # U256(0).to_be_bytes() gives b'' instead b'\x00'. + value_bytes = bytes(value.to_be_bytes32()) + # Now among the obtained value bytes, consider only + # N `least significant bytes`, where N is `byte_num + 1`. + value_bytes = value_bytes[31 - int(byte_num) :] + sign_bit = value_bytes[0] >> 7 + if sign_bit == 0: + result = U256.from_be_bytes(value_bytes) + else: + num_bytes_prepend = U256(32) - (byte_num + U256(1)) + result = U256.from_be_bytes( + bytearray([0xFF] * num_bytes_prepend) + value_bytes + ) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/bitwise.py b/src/ethereum/osaka/vm/instructions/bitwise.py new file mode 100644 index 0000000000..3abb58be48 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/bitwise.py @@ -0,0 +1,240 @@ +""" +Ethereum Virtual Machine (EVM) Bitwise Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM bitwise instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def bitwise_and(evm: Evm) -> None: + """ + Bitwise AND operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x & y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_or(evm: Evm) -> None: + """ + Bitwise OR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x | y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_xor(evm: Evm) -> None: + """ + Bitwise XOR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x ^ y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_not(evm: Evm) -> None: + """ + Bitwise NOT operation of the top element of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, ~x) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def get_byte(evm: Evm) -> None: + """ + For a word (defined by next top element of the stack), retrieve the + Nth byte (0-indexed and defined by top element of stack) from the + left (most significant) to right (least significant). + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_index = pop(evm.stack) + word = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if byte_index >= U256(32): + result = U256(0) + else: + extra_bytes_to_right = U256(31) - byte_index + # Remove the extra bytes in the right + word = word >> (extra_bytes_to_right * U256(8)) + # Remove the extra bytes in the left + word = word & U256(0xFF) + result = word + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shl(evm: Evm) -> None: + """ + Logical shift left (SHL) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = Uint(pop(evm.stack)) + value = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < Uint(256): + result = U256((value << shift) & Uint(U256.MAX_VALUE)) + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shr(evm: Evm) -> None: + """ + Logical shift right (SHR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < U256(256): + result = value >> shift + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_sar(evm: Evm) -> None: + """ + Arithmetic shift right (SAR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = int(pop(evm.stack)) + signed_value = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < 256: + result = U256.from_signed(signed_value >> shift) + elif signed_value >= 0: + result = U256(0) + else: + result = U256.MAX_VALUE + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/block.py b/src/ethereum/osaka/vm/instructions/block.py new file mode 100644 index 0000000000..80644000fd --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/block.py @@ -0,0 +1,255 @@ +""" +Ethereum Virtual Machine (EVM) Block Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM block instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas +from ..stack import pop, push + + +def block_hash(evm: Evm) -> None: + """ + Push the hash of one of the 256 most recent complete blocks onto the + stack. The block number to hash is present at the top of the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackUnderflowError` + If `len(stack)` is less than `1`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `20`. + """ + # STACK + block_number = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_BLOCK_HASH) + + # OPERATION + max_block_number = block_number + Uint(256) + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): + # Default hash to 0, if the block of interest is not yet on the chain + # (including the block which has the current executing transaction), + # or if the block's age is more than 256. + hash = b"\x00" + else: + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def coinbase(evm: Evm) -> None: + """ + Push the current block's beneficiary address (address of the block miner) + onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def timestamp(evm: Evm) -> None: + """ + Push the current block's timestamp onto the stack. Here the timestamp + being referred is actually the unix timestamp in seconds. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.message.block_env.time) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def number(evm: Evm) -> None: + """ + Push the current block's number onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.number)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def prev_randao(evm: Evm) -> None: + """ + Push the `prev_randao` value onto the stack. + + The `prev_randao` value is the random output of the beacon chain's + randomness oracle for the previous block. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_limit(evm: Evm) -> None: + """ + Push the current block's gas limit onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def chain_id(evm: Evm) -> None: + """ + Push the chain id onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.chain_id)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/comparison.py b/src/ethereum/osaka/vm/instructions/comparison.py new file mode 100644 index 0000000000..275455ba53 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/comparison.py @@ -0,0 +1,178 @@ +""" +Ethereum Virtual Machine (EVM) Comparison Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Comparison instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def less_than(evm: Evm) -> None: + """ + Checks if the top element is less than the next top element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_less_than(evm: Evm) -> None: + """ + Signed less-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def greater_than(evm: Evm) -> None: + """ + Checks if the top element is greater than the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_greater_than(evm: Evm) -> None: + """ + Signed greater-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def equal(evm: Evm) -> None: + """ + Checks if the top element is equal to the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left == right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def is_zero(evm: Evm) -> None: + """ + Checks if the top element is equal to 0. Pushes the result back on the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(x == 0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/control_flow.py b/src/ethereum/osaka/vm/instructions/control_flow.py new file mode 100644 index 0000000000..7722661f79 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/control_flow.py @@ -0,0 +1,171 @@ +""" +Ethereum Virtual Machine (EVM) Control Flow Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM control flow instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ...vm.gas import GAS_BASE, GAS_HIGH, GAS_JUMPDEST, GAS_MID, charge_gas +from .. import Evm +from ..exceptions import InvalidJumpDestError +from ..stack import pop, push + + +def stop(evm: Evm) -> None: + """ + Stop further execution of EVM code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + pass + + # OPERATION + evm.running = False + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jump(evm: Evm) -> None: + """ + Alter the program counter to the location specified by the top of the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + + # PROGRAM COUNTER + evm.pc = Uint(jump_dest) + + +def jumpi(evm: Evm) -> None: + """ + Alter the program counter to the specified location if and only if a + condition is true. If the condition is not true, then the program counter + would increase only by 1. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + conditional_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_HIGH) + + # OPERATION + if conditional_value == 0: + destination = evm.pc + Uint(1) + elif jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + else: + destination = jump_dest + + # PROGRAM COUNTER + evm.pc = destination + + +def pc(evm: Evm) -> None: + """ + Push onto the stack the value of the program counter after reaching the + current instruction and without increasing it for the next instruction. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.pc)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_left(evm: Evm) -> None: + """ + Push the amount of available gas (including the corresponding reduction + for the cost of this instruction) onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.gas_left)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jumpdest(evm: Evm) -> None: + """ + Mark a valid destination for jumps. This is a noop, present only + to be used by `JUMP` and `JUMPI` opcodes to verify that their jump is + valid. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_JUMPDEST) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py new file mode 100644 index 0000000000..5ddd12dac8 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/environment.py @@ -0,0 +1,597 @@ +""" +Ethereum Virtual Machine (EVM) Environmental Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM environment related instructions. +""" + +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from ...fork_types import EMPTY_ACCOUNT +from ...state import get_account +from ...utils.address import to_address +from ...vm.memory import buffer_read, memory_write +from .. import Evm +from ..exceptions import OutOfBoundsRead +from ..gas import ( + GAS_BASE, + GAS_BLOBHASH_OPCODE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_COPY, + GAS_FAST_STEP, + GAS_RETURN_DATA_COPY, + GAS_VERY_LOW, + GAS_WARM_ACCESS, + calculate_blob_gas_price, + calculate_gas_extend_memory, + charge_gas, +) +from ..stack import pop, push + + +def address(evm: Evm) -> None: + """ + Pushes the address of the current executing account to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.current_target)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def balance(evm: Evm) -> None: + """ + Pushes the balance of the given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_addresses.add(address) + charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account(evm.message.block_env.state, address).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def origin(evm: Evm) -> None: + """ + Pushes the address of the original transaction sender to the stack. + The origin address can only be an EOA. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def caller(evm: Evm) -> None: + """ + Pushes the address of the caller onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.caller)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callvalue(evm: Evm) -> None: + """ + Push the value (in wei) sent with the call onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.message.value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldataload(evm: Evm) -> None: + """ + Push a word (32 bytes) of the input data belonging to the current + environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + value = buffer_read(evm.message.data, start_index, U256(32)) + + push(evm.stack, U256.from_be_bytes(value)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatasize(evm: Evm) -> None: + """ + Push the size of input data in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.message.data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatacopy(evm: Evm) -> None: + """ + Copy a portion of the input data in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + data_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.message.data, data_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codesize(evm: Evm) -> None: + """ + Push the size of code running in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.code))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codecopy(evm: Evm) -> None: + """ + Copy a portion of the code in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gasprice(evm: Evm) -> None: + """ + Push the gas price used in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.tx_env.gas_price)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodesize(evm: Evm) -> None: + """ + Push the code size of a given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) + + # OPERATION + code = get_account(evm.message.block_env.state, address).code + + codesize = U256(len(code)) + push(evm.stack, codesize) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodecopy(evm: Evm) -> None: + """ + Copy a portion of an account's code to memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + code = get_account(evm.message.block_env.state, address).code + + value = buffer_read(code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatasize(evm: Evm) -> None: + """ + Pushes the size of the return data buffer onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.return_data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatacopy(evm: Evm) -> None: + """ + Copies data from the return data buffer code to memory + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + return_data_start_position = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_RETURN_DATA_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + if Uint(return_data_start_position) + Uint(size) > ulen(evm.return_data): + raise OutOfBoundsRead + + evm.memory += b"\x00" * extend_memory.expand_by + value = evm.return_data[ + return_data_start_position : return_data_start_position + size + ] + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodehash(evm: Evm) -> None: + """ + Returns the keccak256 hash of a contract’s bytecode + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) + + # OPERATION + account = get_account(evm.message.block_env.state, address) + + if account == EMPTY_ACCOUNT: + codehash = U256(0) + else: + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) + + push(evm.stack, codehash) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def self_balance(evm: Evm) -> None: + """ + Pushes the balance of the current address to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_FAST_STEP) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def base_fee(evm: Evm) -> None: + """ + Pushes the base fee of the current block on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_hash(evm: Evm) -> None: + """ + Pushes the versioned hash at a particular index on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BLOBHASH_OPCODE) + + # OPERATION + if int(index) < len(evm.message.tx_env.blob_versioned_hashes): + blob_hash = evm.message.tx_env.blob_versioned_hashes[index] + else: + blob_hash = Bytes32(b"\x00" * 32) + push(evm.stack, U256.from_be_bytes(blob_hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_base_fee(evm: Evm) -> None: + """ + Pushes the blob base fee on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + blob_base_fee = calculate_blob_gas_price( + evm.message.block_env.excess_blob_gas + ) + push(evm.stack, U256(blob_base_fee)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/keccak.py b/src/ethereum/osaka/vm/instructions/keccak.py new file mode 100644 index 0000000000..830d368277 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/keccak.py @@ -0,0 +1,64 @@ +""" +Ethereum Virtual Machine (EVM) Keccak Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM keccak instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_KECCAK256, + GAS_KECCAK256_WORD, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop, push + + +def keccak(evm: Evm) -> None: + """ + Pushes to the stack the Keccak-256 hash of a region of memory. + + This also expands the memory, in case the memory is insufficient to + access the data's memory location. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + word_gas_cost = GAS_KECCAK256_WORD * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_KECCAK256 + word_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + data = memory_read_bytes(evm.memory, memory_start_index, size) + hash = keccak256(data) + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/log.py b/src/ethereum/osaka/vm/instructions/log.py new file mode 100644 index 0000000000..87c06ed6be --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/log.py @@ -0,0 +1,88 @@ +""" +Ethereum Virtual Machine (EVM) Logging Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM logging instructions. +""" +from functools import partial + +from ethereum_types.numeric import Uint + +from ...blocks import Log +from .. import Evm +from ..exceptions import WriteInStaticContext +from ..gas import ( + GAS_LOG, + GAS_LOG_DATA, + GAS_LOG_TOPIC, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop + + +def log_n(evm: Evm, num_topics: int) -> None: + """ + Appends a log entry, having `num_topics` topics, to the evm logs. + + This will also expand the memory if the data (required by the log entry) + corresponding to the memory is not accessible. + + Parameters + ---------- + evm : + The current EVM frame. + num_topics : + The number of topics to be included in the log entry. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + topics = [] + for _ in range(num_topics): + topic = pop(evm.stack).to_be_bytes32() + topics.append(topic) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas( + evm, + GAS_LOG + + GAS_LOG_DATA * Uint(size) + + GAS_LOG_TOPIC * Uint(num_topics) + + extend_memory.cost, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + if evm.message.is_static: + raise WriteInStaticContext + log_entry = Log( + address=evm.message.current_target, + topics=tuple(topics), + data=memory_read_bytes(evm.memory, memory_start_index, size), + ) + + evm.logs = evm.logs + (log_entry,) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +log0 = partial(log_n, num_topics=0) +log1 = partial(log_n, num_topics=1) +log2 = partial(log_n, num_topics=2) +log3 = partial(log_n, num_topics=3) +log4 = partial(log_n, num_topics=4) diff --git a/src/ethereum/osaka/vm/instructions/memory.py b/src/ethereum/osaka/vm/instructions/memory.py new file mode 100644 index 0000000000..89533af37e --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/memory.py @@ -0,0 +1,177 @@ +""" +Ethereum Virtual Machine (EVM) Memory Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Memory instructions. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_BASE, + GAS_COPY, + GAS_VERY_LOW, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def mstore(evm: Evm) -> None: + """ + Stores a word to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(len(value)))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + memory_write(evm.memory, start_position, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mstore8(evm: Evm) -> None: + """ + Stores a byte to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(1))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + normalized_bytes_value = Bytes([value & U256(0xFF)]) + memory_write(evm.memory, start_position, normalized_bytes_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mload(evm: Evm) -> None: + """ + Load word from memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(32))] + ) + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = U256.from_be_bytes( + memory_read_bytes(evm.memory, start_position, U256(32)) + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def msize(evm: Evm) -> None: + """ + Push the size of active memory in bytes onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.memory))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mcopy(evm: Evm) -> None: + """ + Copy the bytes in memory from one location to another. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + destination = pop(evm.stack) + source = pop(evm.stack) + length = pop(evm.stack) + + # GAS + words = ceil32(Uint(length)) // Uint(32) + copy_gas_cost = GAS_COPY * words + + extend_memory = calculate_gas_extend_memory( + evm.memory, [(source, length), (destination, length)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = memory_read_bytes(evm.memory, source, length) + memory_write(evm.memory, destination, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/stack.py b/src/ethereum/osaka/vm/instructions/stack.py new file mode 100644 index 0000000000..2e8a492412 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/stack.py @@ -0,0 +1,209 @@ +""" +Ethereum Virtual Machine (EVM) Stack Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM stack related instructions. +""" + +from functools import partial + +from ethereum_types.numeric import U256, Uint + +from .. import Evm, stack +from ..exceptions import StackUnderflowError +from ..gas import GAS_BASE, GAS_VERY_LOW, charge_gas +from ..memory import buffer_read + + +def pop(evm: Evm) -> None: + """ + Remove item from stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + stack.pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def push_n(evm: Evm, num_bytes: int) -> None: + """ + Pushes a N-byte immediate onto the stack. Push zero if num_bytes is zero. + + Parameters + ---------- + evm : + The current EVM frame. + + num_bytes : + The number of immediate bytes to be read from the code and pushed to + the stack. Push zero if num_bytes is zero. + + """ + # STACK + pass + + # GAS + if num_bytes == 0: + charge_gas(evm, GAS_BASE) + else: + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + data_to_push = U256.from_be_bytes( + buffer_read(evm.code, U256(evm.pc + Uint(1)), U256(num_bytes)) + ) + stack.push(evm.stack, data_to_push) + + # PROGRAM COUNTER + evm.pc += Uint(1) + Uint(num_bytes) + + +def dup_n(evm: Evm, item_number: int) -> None: + """ + Duplicate the Nth stack item (from top of the stack) to the top of stack. + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be duplicated + to the top of stack. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + data_to_duplicate = evm.stack[len(evm.stack) - 1 - item_number] + stack.push(evm.stack, data_to_duplicate) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def swap_n(evm: Evm, item_number: int) -> None: + """ + Swap the top and the `item_number` element of the stack, where + the top of the stack is position zero. + + If `item_number` is zero, this function does nothing (which should not be + possible, since there is no `SWAP0` instruction). + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be swapped + with the top of stack element. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + evm.stack[-1], evm.stack[-1 - item_number] = ( + evm.stack[-1 - item_number], + evm.stack[-1], + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +push0 = partial(push_n, num_bytes=0) +push1 = partial(push_n, num_bytes=1) +push2 = partial(push_n, num_bytes=2) +push3 = partial(push_n, num_bytes=3) +push4 = partial(push_n, num_bytes=4) +push5 = partial(push_n, num_bytes=5) +push6 = partial(push_n, num_bytes=6) +push7 = partial(push_n, num_bytes=7) +push8 = partial(push_n, num_bytes=8) +push9 = partial(push_n, num_bytes=9) +push10 = partial(push_n, num_bytes=10) +push11 = partial(push_n, num_bytes=11) +push12 = partial(push_n, num_bytes=12) +push13 = partial(push_n, num_bytes=13) +push14 = partial(push_n, num_bytes=14) +push15 = partial(push_n, num_bytes=15) +push16 = partial(push_n, num_bytes=16) +push17 = partial(push_n, num_bytes=17) +push18 = partial(push_n, num_bytes=18) +push19 = partial(push_n, num_bytes=19) +push20 = partial(push_n, num_bytes=20) +push21 = partial(push_n, num_bytes=21) +push22 = partial(push_n, num_bytes=22) +push23 = partial(push_n, num_bytes=23) +push24 = partial(push_n, num_bytes=24) +push25 = partial(push_n, num_bytes=25) +push26 = partial(push_n, num_bytes=26) +push27 = partial(push_n, num_bytes=27) +push28 = partial(push_n, num_bytes=28) +push29 = partial(push_n, num_bytes=29) +push30 = partial(push_n, num_bytes=30) +push31 = partial(push_n, num_bytes=31) +push32 = partial(push_n, num_bytes=32) + +dup1 = partial(dup_n, item_number=0) +dup2 = partial(dup_n, item_number=1) +dup3 = partial(dup_n, item_number=2) +dup4 = partial(dup_n, item_number=3) +dup5 = partial(dup_n, item_number=4) +dup6 = partial(dup_n, item_number=5) +dup7 = partial(dup_n, item_number=6) +dup8 = partial(dup_n, item_number=7) +dup9 = partial(dup_n, item_number=8) +dup10 = partial(dup_n, item_number=9) +dup11 = partial(dup_n, item_number=10) +dup12 = partial(dup_n, item_number=11) +dup13 = partial(dup_n, item_number=12) +dup14 = partial(dup_n, item_number=13) +dup15 = partial(dup_n, item_number=14) +dup16 = partial(dup_n, item_number=15) + +swap1 = partial(swap_n, item_number=1) +swap2 = partial(swap_n, item_number=2) +swap3 = partial(swap_n, item_number=3) +swap4 = partial(swap_n, item_number=4) +swap5 = partial(swap_n, item_number=5) +swap6 = partial(swap_n, item_number=6) +swap7 = partial(swap_n, item_number=7) +swap8 = partial(swap_n, item_number=8) +swap9 = partial(swap_n, item_number=9) +swap10 = partial(swap_n, item_number=10) +swap11 = partial(swap_n, item_number=11) +swap12 = partial(swap_n, item_number=12) +swap13 = partial(swap_n, item_number=13) +swap14 = partial(swap_n, item_number=14) +swap15 = partial(swap_n, item_number=15) +swap16 = partial(swap_n, item_number=16) diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py new file mode 100644 index 0000000000..65a0d5a9b6 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -0,0 +1,184 @@ +""" +Ethereum Virtual Machine (EVM) Storage Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM storage related instructions. +""" +from ethereum_types.numeric import Uint + +from ...state import ( + get_storage, + get_storage_original, + get_transient_storage, + set_storage, + set_transient_storage, +) +from .. import Evm +from ..exceptions import OutOfGasError, WriteInStaticContext +from ..gas import ( + GAS_CALL_STIPEND, + GAS_COLD_SLOAD, + GAS_STORAGE_CLEAR_REFUND, + GAS_STORAGE_SET, + GAS_STORAGE_UPDATE, + GAS_WARM_ACCESS, + charge_gas, +) +from ..stack import pop, push + + +def sload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + storage of the current account. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + if (evm.message.current_target, key) in evm.accessed_storage_keys: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + charge_gas(evm, GAS_COLD_SLOAD) + + # OPERATION + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) + + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's storage. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + 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 + ) + current_value = get_storage(state, evm.message.current_target, key) + + gas_cost = Uint(0) + + if (evm.message.current_target, key) not in evm.accessed_storage_keys: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + gas_cost += GAS_COLD_SLOAD + + if original_value == current_value and current_value != new_value: + if original_value == 0: + gas_cost += GAS_STORAGE_SET + else: + gas_cost += GAS_STORAGE_UPDATE - GAS_COLD_SLOAD + else: + gas_cost += GAS_WARM_ACCESS + + # Refund Counter Calculation + if current_value != new_value: + if original_value != 0 and current_value != 0 and new_value == 0: + # Storage is cleared for the first time in the transaction + evm.refund_counter += int(GAS_STORAGE_CLEAR_REFUND) + + if original_value != 0 and current_value == 0: + # Gas refund issued earlier to be reversed + evm.refund_counter -= int(GAS_STORAGE_CLEAR_REFUND) + + if original_value == new_value: + # Storage slot being restored to its original value + if original_value == 0: + # Slot was originally empty and was SET earlier + evm.refund_counter += int(GAS_STORAGE_SET - GAS_WARM_ACCESS) + else: + # Slot was originally non-empty and was UPDATED earlier + evm.refund_counter += int( + GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS + ) + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + set_storage(state, evm.message.current_target, key, new_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + transient storage of the current account. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + + # OPERATION + value = get_transient_storage( + evm.message.tx_env.transient_storage, evm.message.current_target, key + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's transient storage. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + if evm.message.is_static: + raise WriteInStaticContext + set_transient_storage( + evm.message.tx_env.transient_storage, + 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 new file mode 100644 index 0000000000..88ba466e8e --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -0,0 +1,755 @@ +""" +Ethereum Virtual Machine (EVM) System Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM system related instructions. +""" +from ethereum_types.bytes import Bytes0 +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import ceil32 + +from ...fork_types import Address +from ...state import ( + account_exists_and_is_empty, + 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, + to_address, +) +from ...vm.eoa_delegation import access_delegation +from .. import ( + Evm, + Message, + incorporate_child_on_error, + incorporate_child_on_success, +) +from ..exceptions import OutOfGasError, Revert, WriteInStaticContext +from ..gas import ( + GAS_CALL_VALUE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_CREATE, + GAS_KECCAK256_WORD, + GAS_NEW_ACCOUNT, + GAS_SELF_DESTRUCT, + GAS_SELF_DESTRUCT_NEW_ACCOUNT, + GAS_WARM_ACCESS, + GAS_ZERO, + calculate_gas_extend_memory, + calculate_message_call_gas, + charge_gas, + init_code_cost, + max_message_call_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def generic_create( + evm: Evm, + endowment: U256, + contract_address: Address, + memory_start_position: U256, + memory_size: U256, + init_code_gas: Uint, +) -> None: + """ + Core logic used by the `CREATE*` family of opcodes. + """ + # This import causes a circular import error + # if it's not moved inside this method + from ...vm.interpreter import ( + MAX_CODE_SIZE, + STACK_DEPTH_LIMIT, + process_create_message, + ) + + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + if len(call_data) > 2 * MAX_CODE_SIZE: + raise OutOfGasError + + evm.accessed_addresses.add(contract_address) + + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) + evm.gas_left -= create_message_gas + if evm.message.is_static: + raise WriteInStaticContext + evm.return_data = b"" + + sender_address = evm.message.current_target + sender = get_account(evm.message.block_env.state, sender_address) + + if ( + sender.balance < endowment + or sender.nonce == Uint(2**64 - 1) + or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT + ): + evm.gas_left += create_message_gas + push(evm.stack, U256(0)) + return + + 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 + ) + push(evm.stack, U256(0)) + return + + increment_nonce(evm.message.block_env.state, evm.message.current_target) + + child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, + caller=evm.message.current_target, + target=Bytes0(), + gas=create_message_gas, + value=endowment, + data=b"", + code=call_data, + current_target=contract_address, + depth=evm.message.depth + Uint(1), + code_address=None, + should_transfer_value=True, + is_static=False, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + parent_evm=evm, + ) + child_evm = process_create_message(child_message) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = b"" + push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) + + +def create(evm: Evm) -> None: + """ + Creates a new account with associated code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + init_code_gas = init_code_cost(Uint(memory_size)) + + charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) + + # OPERATION + 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, + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + init_code_gas, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def create2(evm: Evm) -> None: + """ + Creates a new account with associated code. + + It's similar to CREATE opcode except that the address of new account + depends on the init_code instead of the nonce of sender. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + salt = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + call_data_words = ceil32(Uint(memory_size)) // Uint(32) + init_code_gas = init_code_cost(Uint(memory_size)) + charge_gas( + evm, + GAS_CREATE + + GAS_KECCAK256_WORD * call_data_words + + extend_memory.cost + + init_code_gas, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + contract_address = compute_create2_contract_address( + evm.message.current_target, + salt, + memory_read_bytes(evm.memory, memory_start_position, memory_size), + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + init_code_gas, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def return_(evm: Evm) -> None: + """ + Halts execution returning output data. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + + charge_gas(evm, GAS_ZERO + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + evm.output = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + + evm.running = False + + # PROGRAM COUNTER + pass + + +def generic_call( + evm: Evm, + gas: Uint, + value: U256, + caller: Address, + to: Address, + code_address: Address, + should_transfer_value: bool, + is_staticcall: bool, + memory_input_start_position: U256, + memory_input_size: U256, + memory_output_start_position: U256, + memory_output_size: U256, + code: bytes, + is_delegated_code: bool, +) -> None: + """ + Perform the core logic of the `CALL*` family of opcodes. + """ + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message + + evm.return_data = b"" + + if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: + evm.gas_left += gas + push(evm.stack, U256(0)) + return + + call_data = memory_read_bytes( + evm.memory, memory_input_start_position, memory_input_size + ) + code = get_account(evm.message.block_env.state, code_address).code + + if is_delegated_code and len(code) == 0: + evm.gas_left += gas + push(evm.stack, U256(1)) + return + + child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, + caller=caller, + target=to, + gas=gas, + value=value, + data=call_data, + code=code, + current_target=to, + depth=evm.message.depth + Uint(1), + code_address=code_address, + should_transfer_value=should_transfer_value, + is_static=True if is_staticcall else evm.message.is_static, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + parent_evm=evm, + ) + child_evm = process_message(child_message) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(1)) + + actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + memory_write( + evm.memory, + memory_output_start_position, + child_evm.output[:actual_output_size], + ) + + +def call(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + code_address = to + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + create_gas_cost = ( + Uint(0) + if is_account_alive(evm.message.block_env.state, to) or value == 0 + else GAS_NEW_ACCOUNT + ) + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + create_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + 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 + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + code_address, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + is_delegated_code, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callcode(evm: Evm) -> None: + """ + Message-call into this account with alternative account’s code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + to = evm.message.current_target + + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + sender_balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + code_address, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + is_delegated_code, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def selfdestruct(evm: Evm) -> None: + """ + Halt execution and register account for later deletion. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + beneficiary = to_address(pop(evm.stack)) + + # GAS + gas_cost = GAS_SELF_DESTRUCT + if beneficiary not in evm.accessed_addresses: + 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 + ): + gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + + originator = evm.message.current_target + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance + + move_ether( + evm.message.block_env.state, + originator, + beneficiary, + originator_balance, + ) + + # register account for deletion only if it was created + # in the same transaction + if originator in evm.message.block_env.state.created_accounts: + # If beneficiary is the same as originator, then + # the ether is burnt. + set_account_balance(evm.message.block_env.state, originator, U256(0)) + evm.accounts_to_delete.add(originator) + + # mark beneficiary as touched + if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): + evm.touched_accounts.add(beneficiary) + + # HALT the execution + evm.running = False + + # PROGRAM COUNTER + pass + + +def delegatecall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + message_call_gas = calculate_message_call_gas( + U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + evm.message.value, + evm.message.caller, + evm.message.current_target, + code_address, + False, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + is_delegated_code, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def staticcall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + code_address = to + ( + is_delegated_code, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + message_call_gas = calculate_message_call_gas( + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + U256(0), + evm.message.current_target, + to, + code_address, + True, + True, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + is_delegated_code, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def revert(evm: Evm) -> None: + """ + Stop execution and revert state changes, without consuming all provided gas + and also has the ability to return a reason + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + charge_gas(evm, extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + output = memory_read_bytes(evm.memory, memory_start_index, size) + evm.output = bytes(output) + raise Revert + + # PROGRAM COUNTER + pass diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py new file mode 100644 index 0000000000..1c4855ea6e --- /dev/null +++ b/src/ethereum/osaka/vm/interpreter.py @@ -0,0 +1,328 @@ +""" +Ethereum Virtual Machine (EVM) Interpreter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +A straightforward interpreter that executes EVM code. +""" +from dataclasses import dataclass +from typing import Optional, Set, Tuple + +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.exceptions import EthereumException +from ethereum.trace import ( + EvmStop, + OpEnd, + OpException, + OpStart, + PrecompileEnd, + PrecompileStart, + TransactionEnd, + evm_trace, +) + +from ..blocks import Log +from ..fork_types import Address +from ..state import ( + account_exists_and_is_empty, + account_has_code_or_nonce, + account_has_storage, + begin_transaction, + commit_transaction, + destroy_storage, + increment_nonce, + mark_account_created, + move_ether, + rollback_transaction, + set_code, + touch_account, +) +from ..vm import Message +from ..vm.eoa_delegation import set_delegation +from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from . import Evm +from .exceptions import ( + AddressCollision, + ExceptionalHalt, + InvalidContractPrefix, + InvalidOpcode, + OutOfGasError, + Revert, + StackDepthLimitError, +) +from .instructions import Ops, op_implementation +from .runtime import get_valid_jump_destinations + +STACK_DEPTH_LIMIT = Uint(1024) +MAX_CODE_SIZE = 0x6000 + + +@dataclass +class MessageCallOutput: + """ + Output of a particular message call + + Contains the following: + + 1. `gas_left`: remaining gas after execution. + 2. `refund_counter`: gas to refund after execution. + 3. `logs`: list of `Log` generated during execution. + 4. `accounts_to_delete`: Contracts which have self-destructed. + 5. `touched_accounts`: Accounts that have been touched. + 6. `error`: The error from the execution if any. + 7. `return_data`: The output of the execution. + """ + + gas_left: Uint + refund_counter: U256 + logs: Tuple[Log, ...] + accounts_to_delete: Set[Address] + touched_accounts: Set[Address] + error: Optional[EthereumException] + return_data: Bytes + + +def process_message_call(message: Message) -> MessageCallOutput: + """ + If `message.current` is empty then it creates a smart contract + else it executes a call from the `message.caller` to the `message.target`. + + Parameters + ---------- + message : + Transaction specific items. + + Returns + ------- + output : `MessageCallOutput` + Output of the message call + """ + 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) + if is_collision: + return MessageCallOutput( + Uint(0), + U256(0), + tuple(), + set(), + set(), + AddressCollision(), + Bytes(b""), + ) + else: + evm = process_create_message(message) + else: + if message.tx_env.authorizations != (): + refund_counter += set_delegation(message) + evm = process_message(message) + if account_exists_and_is_empty( + block_env.state, Address(message.target) + ): + evm.touched_accounts.add(Address(message.target)) + + if evm.error: + logs: Tuple[Log, ...] = () + accounts_to_delete = set() + touched_accounts = set() + else: + logs = evm.logs + accounts_to_delete = evm.accounts_to_delete + touched_accounts = evm.touched_accounts + refund_counter += U256(evm.refund_counter) + + tx_end = TransactionEnd( + int(message.gas) - int(evm.gas_left), evm.output, evm.error + ) + evm_trace(evm, tx_end) + + return MessageCallOutput( + gas_left=evm.gas_left, + refund_counter=refund_counter, + logs=logs, + accounts_to_delete=accounts_to_delete, + touched_accounts=touched_accounts, + error=evm.error, + return_data=evm.output, + ) + + +def process_create_message(message: Message) -> Evm: + """ + Executes a call to create a smart contract. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: :py:class:`~ethereum.osaka.vm.Evm` + Items containing execution specific objects. + """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage + # take snapshot of state before processing the message + begin_transaction(state, transient_storage) + + # If the address where the account is being created has storage, it is + # destroyed. This can only happen in the following highly unlikely + # circumstances: + # * The address created by a `CREATE` call collides with a subsequent + # `CREATE` or `CREATE2` call. + # * The first `CREATE` happened before Spurious Dragon and left empty + # code. + destroy_storage(state, 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. + mark_account_created(state, message.current_target) + + increment_nonce(state, message.current_target) + evm = process_message(message) + if not evm.error: + contract_code = evm.output + contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT + try: + if len(contract_code) > 0: + if contract_code[0] == 0xEF: + raise InvalidContractPrefix + charge_gas(evm, contract_code_gas) + if len(contract_code) > MAX_CODE_SIZE: + raise OutOfGasError + except ExceptionalHalt as error: + rollback_transaction(state, transient_storage) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + else: + set_code(state, message.current_target, contract_code) + commit_transaction(state, transient_storage) + else: + rollback_transaction(state, transient_storage) + return evm + + +def process_message(message: Message) -> Evm: + """ + Executes a call to create a smart contract. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: :py:class:`~ethereum.osaka.vm.Evm` + Items containing execution specific objects + """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage + if message.depth > STACK_DEPTH_LIMIT: + raise StackDepthLimitError("Stack depth limit reached") + + # take snapshot of state before processing the message + begin_transaction(state, transient_storage) + + touch_account(state, message.current_target) + + if message.should_transfer_value and message.value != 0: + move_ether( + state, message.caller, message.current_target, message.value + ) + + evm = execute_code(message) + if evm.error: + # revert state to the last saved checkpoint + # since the message call resulted in an error + rollback_transaction(state, transient_storage) + else: + commit_transaction(state, transient_storage) + return evm + + +def execute_code(message: Message) -> Evm: + """ + Executes bytecode present in the `message`. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + evm: `ethereum.vm.EVM` + Items containing execution specific objects + """ + code = message.code + valid_jump_destinations = get_valid_jump_destinations(code) + + evm = Evm( + pc=Uint(0), + stack=[], + memory=bytearray(), + code=code, + gas_left=message.gas, + valid_jump_destinations=valid_jump_destinations, + logs=(), + refund_counter=0, + running=True, + message=message, + output=b"", + accounts_to_delete=set(), + touched_accounts=set(), + return_data=b"", + error=None, + accessed_addresses=message.accessed_addresses, + accessed_storage_keys=message.accessed_storage_keys, + ) + try: + if evm.message.code_address in PRE_COMPILED_CONTRACTS: + evm_trace(evm, PrecompileStart(evm.message.code_address)) + PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) + evm_trace(evm, PrecompileEnd()) + return evm + + while evm.running and evm.pc < ulen(evm.code): + try: + op = Ops(evm.code[evm.pc]) + except ValueError: + raise InvalidOpcode(evm.code[evm.pc]) + + evm_trace(evm, OpStart(op)) + op_implementation[op](evm) + evm_trace(evm, OpEnd()) + + evm_trace(evm, EvmStop(Ops.STOP)) + + except ExceptionalHalt as error: + evm_trace(evm, OpException(error)) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + except Revert as error: + evm_trace(evm, OpException(error)) + evm.error = error + return evm diff --git a/src/ethereum/osaka/vm/memory.py b/src/ethereum/osaka/vm/memory.py new file mode 100644 index 0000000000..aa2e7fdd57 --- /dev/null +++ b/src/ethereum/osaka/vm/memory.py @@ -0,0 +1,81 @@ +""" +Ethereum Virtual Machine (EVM) Memory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM memory operations. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.byte import right_pad_zero_bytes + + +def memory_write( + memory: bytearray, start_position: U256, value: Bytes +) -> None: + """ + Writes to memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + value : + Data to write to memory. + """ + memory[start_position : int(start_position) + len(value)] = value + + +def memory_read_bytes( + memory: bytearray, start_position: U256, size: U256 +) -> bytearray: + """ + Read bytes from memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return memory[start_position : Uint(start_position) + Uint(size)] + + +def buffer_read(buffer: Bytes, start_position: U256, size: U256) -> Bytes: + """ + Read bytes from a buffer. Padding with zeros if necessary. + + Parameters + ---------- + buffer : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return right_pad_zero_bytes( + buffer[start_position : Uint(start_position) + Uint(size)], size + ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/__init__.py new file mode 100644 index 0000000000..8ab92bb0f3 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/__init__.py @@ -0,0 +1,54 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Addresses of precompiled contracts and mappings to their +implementations. +""" + +from ...utils.hexadecimal import hex_to_address + +__all__ = ( + "ECRECOVER_ADDRESS", + "SHA256_ADDRESS", + "RIPEMD160_ADDRESS", + "IDENTITY_ADDRESS", + "MODEXP_ADDRESS", + "ALT_BN128_ADD_ADDRESS", + "ALT_BN128_MUL_ADDRESS", + "ALT_BN128_PAIRING_CHECK_ADDRESS", + "BLAKE2F_ADDRESS", + "POINT_EVALUATION_ADDRESS", + "BLS12_G1_ADD_ADDRESS", + "BLS12_G1_MSM_ADDRESS", + "BLS12_G2_ADD_ADDRESS", + "BLS12_G2_MSM_ADDRESS", + "BLS12_PAIRING_ADDRESS", + "BLS12_MAP_FP_TO_G1_ADDRESS", + "BLS12_MAP_FP2_TO_G2_ADDRESS", +) + +ECRECOVER_ADDRESS = hex_to_address("0x01") +SHA256_ADDRESS = hex_to_address("0x02") +RIPEMD160_ADDRESS = hex_to_address("0x03") +IDENTITY_ADDRESS = hex_to_address("0x04") +MODEXP_ADDRESS = hex_to_address("0x05") +ALT_BN128_ADD_ADDRESS = hex_to_address("0x06") +ALT_BN128_MUL_ADDRESS = hex_to_address("0x07") +ALT_BN128_PAIRING_CHECK_ADDRESS = hex_to_address("0x08") +BLAKE2F_ADDRESS = hex_to_address("0x09") +POINT_EVALUATION_ADDRESS = hex_to_address("0x0a") +BLS12_G1_ADD_ADDRESS = hex_to_address("0x0b") +BLS12_G1_MSM_ADDRESS = hex_to_address("0x0c") +BLS12_G2_ADD_ADDRESS = hex_to_address("0x0d") +BLS12_G2_MSM_ADDRESS = hex_to_address("0x0e") +BLS12_PAIRING_ADDRESS = hex_to_address("0x0f") +BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10") +BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11") diff --git a/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py new file mode 100644 index 0000000000..dc75b40ac6 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py @@ -0,0 +1,154 @@ +""" +Ethereum Virtual Machine (EVM) ALT_BN128 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ALT_BN128 precompiled contracts. +""" +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.alt_bn128 import ( + ALT_BN128_CURVE_ORDER, + ALT_BN128_PRIME, + BNF, + BNF2, + BNF12, + BNP, + BNP2, + pairing, +) + +from ...vm import Evm +from ...vm.gas import charge_gas +from ...vm.memory import buffer_read +from ..exceptions import OutOfGasError + + +def alt_bn128_add(evm: Evm) -> None: + """ + The ALT_BN128 addition precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(150)) + + # OPERATION + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0_value = int(U256.from_be_bytes(x0_bytes)) + y0_bytes = buffer_read(data, U256(32), U256(32)) + y0_value = int(U256.from_be_bytes(y0_bytes)) + x1_bytes = buffer_read(data, U256(64), U256(32)) + x1_value = int(U256.from_be_bytes(x1_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1_value = int(U256.from_be_bytes(y1_bytes)) + + for i in (x0_value, y0_value, x1_value, y1_value): + if i >= ALT_BN128_PRIME: + raise OutOfGasError + + try: + p0 = BNP(BNF(x0_value), BNF(y0_value)) + p1 = BNP(BNF(x1_value), BNF(y1_value)) + except ValueError: + raise OutOfGasError + + p = p0 + p1 + + evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + + +def alt_bn128_mul(evm: Evm) -> None: + """ + The ALT_BN128 multiplication precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(6000)) + + # OPERATION + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0_value = int(U256.from_be_bytes(x0_bytes)) + y0_bytes = buffer_read(data, U256(32), U256(32)) + y0_value = int(U256.from_be_bytes(y0_bytes)) + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) + + for i in (x0_value, y0_value): + if i >= ALT_BN128_PRIME: + raise OutOfGasError + + try: + p0 = BNP(BNF(x0_value), BNF(y0_value)) + except ValueError: + raise OutOfGasError + + p = p0.mul_by(n) + + evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() + + +def alt_bn128_pairing_check(evm: Evm) -> None: + """ + The ALT_BN128 pairing check precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(34000 * (len(data) // 192) + 45000)) + + # OPERATION + if len(data) % 192 != 0: + raise OutOfGasError + result = BNF12.from_int(1) + for i in range(len(data) // 192): + values = [] + for j in range(6): + value = int( + U256.from_be_bytes( + data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] + ) + ) + if value >= ALT_BN128_PRIME: + raise OutOfGasError + values.append(value) + + try: + p = BNP(BNF(values[0]), BNF(values[1])) + q = BNP2( + BNF2((values[3], values[2])), BNF2((values[5], values[4])) + ) + except ValueError: + raise OutOfGasError() + if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): + raise OutOfGasError + if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): + raise OutOfGasError + if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): + result = result * pairing(q, p) + + if result == BNF12.from_int(1): + evm.output = U256(1).to_be_bytes32() + else: + evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py b/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py new file mode 100644 index 0000000000..0d86ba6e85 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py @@ -0,0 +1,41 @@ +""" +Ethereum Virtual Machine (EVM) Blake2 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `Blake2` precompiled contract. +""" +from ethereum.crypto.blake2 import Blake2b + +from ...vm import Evm +from ...vm.gas import GAS_BLAKE2_PER_ROUND, charge_gas +from ..exceptions import InvalidParameter + + +def blake2f(evm: Evm) -> None: + """ + Writes the Blake2 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + if len(data) != 213: + raise InvalidParameter + + blake2b = Blake2b() + rounds, h, m, t_0, t_1, f = blake2b.get_blake2_parameters(data) + + charge_gas(evm, GAS_BLAKE2_PER_ROUND * rounds) + if f not in [0, 1]: + raise InvalidParameter + + evm.output = blake2b.compress(rounds, h, m, t_0, t_1, f) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py new file mode 100644 index 0000000000..2126a6ab39 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py @@ -0,0 +1,583 @@ +""" +BLS12 381 Precompile +^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Precompile for BLS12-381 curve operations. +""" +from typing import Tuple, Union + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import ( + FQ, + FQ2, + b, + b2, + curve_order, + is_on_curve, + multiply, +) +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.typing import Point2D + +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter + +P = FQ.field_modulus + +G1_K_DISCOUNT = [ + 1000, + 949, + 848, + 797, + 764, + 750, + 738, + 728, + 719, + 712, + 705, + 698, + 692, + 687, + 682, + 677, + 673, + 669, + 665, + 661, + 658, + 654, + 651, + 648, + 645, + 642, + 640, + 637, + 635, + 632, + 630, + 627, + 625, + 623, + 621, + 619, + 617, + 615, + 613, + 611, + 609, + 608, + 606, + 604, + 603, + 601, + 599, + 598, + 596, + 595, + 593, + 592, + 591, + 589, + 588, + 586, + 585, + 584, + 582, + 581, + 580, + 579, + 577, + 576, + 575, + 574, + 573, + 572, + 570, + 569, + 568, + 567, + 566, + 565, + 564, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 551, + 550, + 549, + 548, + 547, + 547, + 546, + 545, + 544, + 543, + 542, + 541, + 540, + 540, + 539, + 538, + 537, + 536, + 536, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 529, + 528, + 528, + 527, + 526, + 525, + 525, + 524, + 523, + 522, + 522, + 521, + 520, + 520, + 519, +] + +G2_K_DISCOUNT = [ + 1000, + 1000, + 923, + 884, + 855, + 832, + 812, + 796, + 782, + 770, + 759, + 749, + 740, + 732, + 724, + 717, + 711, + 704, + 699, + 693, + 688, + 683, + 679, + 674, + 670, + 666, + 663, + 659, + 655, + 652, + 649, + 646, + 643, + 640, + 637, + 634, + 632, + 629, + 627, + 624, + 622, + 620, + 618, + 615, + 613, + 611, + 609, + 607, + 606, + 604, + 602, + 600, + 598, + 597, + 595, + 593, + 592, + 590, + 589, + 587, + 586, + 584, + 583, + 582, + 580, + 579, + 578, + 576, + 575, + 574, + 573, + 571, + 570, + 569, + 568, + 567, + 566, + 565, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 552, + 551, + 550, + 549, + 548, + 547, + 546, + 545, + 545, + 544, + 543, + 542, + 541, + 541, + 540, + 539, + 538, + 537, + 537, + 536, + 535, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 530, + 529, + 528, + 528, + 527, + 526, + 526, + 525, + 524, + 524, +] + +G1_MAX_DISCOUNT = 519 +G2_MAX_DISCOUNT = 524 +MULTIPLIER = Uint(1000) + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G1 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G1 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("Input should be 128 bytes long") + + x = int.from_bytes(data[:64], "big") + y = int.from_bytes(data[64:], "big") + + if x >= P: + raise InvalidParameter("Invalid field element") + if y >= P: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def G1_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G1 point to 128 bytes. + + Parameters + ---------- + point : + The G1 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 128 + + x, y = point + + x_bytes = int(x).to_bytes(64, "big") + y_bytes = int(y).to_bytes(64, "big") + + return x_bytes + y_bytes + + +def decode_G1_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 160 bytes to a G1 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G1 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 160: + InvalidParameter("Input should be 160 bytes long") + + p = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(128), U256(32)), "big") + + return p, m + + +def bytes_to_FQ( + data: Bytes, optimized: bool = False +) -> Union[FQ, OPTIMIZED_FQ]: + """ + Decode 64 bytes to a FQ element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ implementation. + + Returns + ------- + fq : Union[FQ, OPTIMIZED_FQ] + The FQ element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 64: + raise InvalidParameter("FQ should be 64 bytes long") + + c = int.from_bytes(data[:64], "big") + + if c >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ(c) + else: + return FQ(c) + + +def bytes_to_FQ2( + data: Bytes, optimized: bool = False +) -> Union[FQ2, OPTIMIZED_FQ2]: + """ + Decode 128 bytes to a FQ2 element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ2 implementation. + + Returns + ------- + fq2 : Union[FQ2, OPTIMIZED_FQ2] + The FQ2 element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 128: + raise InvalidParameter("FQ2 input should be 128 bytes long") + c_0 = int.from_bytes(data[:64], "big") + c_1 = int.from_bytes(data[64:], "big") + + if c_0 >= P: + raise InvalidParameter("Invalid field element") + if c_1 >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ2((c_0, c_1)) + else: + return FQ2((c_0, c_1)) + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 256 bytes to a G2 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G2 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 256: + raise InvalidParameter("G2 should be 256 bytes long") + + x = bytes_to_FQ2(data[:128]) + y = bytes_to_FQ2(data[128:]) + + assert isinstance(x, FQ2) and isinstance(y, FQ2) + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point + + +def FQ2_to_bytes(fq2: FQ2) -> Bytes: + """ + Encode a FQ2 point to 128 bytes. + + Parameters + ---------- + fq2 : + The FQ2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + c_0, c_1 = fq2.coeffs + return int(c_0).to_bytes(64, "big") + int(c_1).to_bytes(64, "big") + + +def G2_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G2 point to 256 bytes. + + Parameters + ---------- + point : + The G2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 256 + + x, y = point + + return FQ2_to_bytes(x) + FQ2_to_bytes(y) + + +def decode_G2_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 288 bytes to a G2 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G2 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 288: + InvalidParameter("Input should be 288 bytes long") + + p = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(256), U256(32)), "big") + + return p, m diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py new file mode 100644 index 0000000000..541395de40 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G1 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G1, map_to_curve_G1 +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G1_ADD, + GAS_BLS_G1_MAP, + GAS_BLS_G1_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G1_K_DISCOUNT, + G1_MAX_DISCOUNT, + MULTIPLIER, + G1_to_bytes, + bytes_to_FQ, + bytes_to_G1, + decode_G1_scalar_pair, +) + +LENGTH_PER_PAIR = 160 + + +def bls12_g1_add(evm: Evm) -> None: + """ + The bls12_381 G1 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 256: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_ADD)) + + # OPERATION + p1 = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + p2 = bytes_to_G1(buffer_read(data, U256(128), U256(128))) + + result = add(p1, p2) + + evm.output = G1_to_bytes(result) + + +def bls12_g1_msm(evm: Evm) -> None: + """ + The bls12_381 G1 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G1_K_DISCOUNT[k - 1]) + else: + discount = Uint(G1_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G1_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G1_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G1_to_bytes(result) + + +def bls12_map_fp_to_g1(evm: Evm) -> None: + """ + Precompile to map field element to G1. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 64: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_MAP)) + + # OPERATION + field_element = bytes_to_FQ(data, True) + assert isinstance(field_element, OPTIMIZED_FQ) + + g1_uncompressed = clear_cofactor_G1(map_to_curve_G1(field_element)) + g1_normalised = normalize(g1_uncompressed) + + evm.output = G1_to_bytes(g1_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py new file mode 100644 index 0000000000..bda7f3641f --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 G2 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G2 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G2, map_to_curve_G2 +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G2_ADD, + GAS_BLS_G2_MAP, + GAS_BLS_G2_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G2_K_DISCOUNT, + G2_MAX_DISCOUNT, + MULTIPLIER, + G2_to_bytes, + bytes_to_FQ2, + bytes_to_G2, + decode_G2_scalar_pair, +) + +LENGTH_PER_PAIR = 288 + + +def bls12_g2_add(evm: Evm) -> None: + """ + The bls12_381 G2 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 512: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_ADD)) + + # OPERATION + p1 = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + p2 = bytes_to_G2(buffer_read(data, U256(256), U256(256))) + + result = add(p1, p2) + + evm.output = G2_to_bytes(result) + + +def bls12_g2_msm(evm: Evm) -> None: + """ + The bls12_381 G2 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G2_K_DISCOUNT[k - 1]) + else: + discount = Uint(G2_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G2_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G2_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G2_to_bytes(result) + + +def bls12_map_fp2_to_g2(evm: Evm) -> None: + """ + Precompile to map field element to G2. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 128: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_MAP)) + + # OPERATION + field_element = bytes_to_FQ2(data, True) + assert isinstance(field_element, OPTIMIZED_FQ2) + + g2_uncompressed = clear_cofactor_G2(map_to_curve_G2(field_element)) + g2_normalised = normalize(g2_uncompressed) + + evm.output = G2_to_bytes(g2_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py new file mode 100644 index 0000000000..2a03a6897a --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py @@ -0,0 +1,67 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 PAIRING PRE-COMPILE +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the BLS12 381 pairing pre-compile. +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import FQ12, curve_order, multiply +from py_ecc.bls12_381.bls12_381_pairing import pairing + +from ....vm import Evm +from ....vm.gas import charge_gas +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import bytes_to_G1, bytes_to_G2 + + +def bls12_pairing(evm: Evm) -> None: + """ + The bls12_381 pairing precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid or if sub-group check fails. + """ + data = evm.message.data + if len(data) == 0 or len(data) % 384 != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // 384 + gas_cost = Uint(32600 * k + 37700) + charge_gas(evm, gas_cost) + + # OPERATION + result = FQ12.one() + for i in range(k): + g1_start = Uint(384 * i) + g2_start = Uint(384 * i + 128) + + g1_point = bytes_to_G1(buffer_read(data, U256(g1_start), U256(128))) + if multiply(g1_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + g2_point = bytes_to_G2(buffer_read(data, U256(g2_start), U256(256))) + if multiply(g2_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + result *= pairing(g2_point, g1_point) + + if result == FQ12.one(): + evm.output = b"\x00" * 31 + b"\x01" + else: + evm.output = b"\x00" * 32 diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py b/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py new file mode 100644 index 0000000000..1f047d3a44 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py @@ -0,0 +1,63 @@ +""" +Ethereum Virtual Machine (EVM) ECRECOVER PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ECRECOVER precompiled contract. +""" +from ethereum_types.numeric import U256 + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError +from ethereum.utils.byte import left_pad_zero_bytes + +from ...vm import Evm +from ...vm.gas import GAS_ECRECOVER, charge_gas +from ...vm.memory import buffer_read + + +def ecrecover(evm: Evm) -> None: + """ + Decrypts the address using elliptic curve DSA recovery mechanism and writes + the address to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, GAS_ECRECOVER) + + # OPERATION + message_hash_bytes = buffer_read(data, U256(0), U256(32)) + message_hash = Hash32(message_hash_bytes) + v = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + r = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + s = U256.from_be_bytes(buffer_read(data, U256(96), U256(32))) + + if v != U256(27) and v != U256(28): + return + if U256(0) >= r or r >= SECP256K1N: + return + if U256(0) >= s or s >= SECP256K1N: + return + + try: + public_key = secp256k1_recover(r, s, v - U256(27), message_hash) + except InvalidSignatureError: + # unable to extract public key + return + + address = keccak256(public_key)[12:32] + padded_address = left_pad_zero_bytes(address, 32) + evm.output = padded_address diff --git a/src/ethereum/osaka/vm/precompiled_contracts/identity.py b/src/ethereum/osaka/vm/precompiled_contracts/identity.py new file mode 100644 index 0000000000..88729c96d7 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/identity.py @@ -0,0 +1,38 @@ +""" +Ethereum Virtual Machine (EVM) IDENTITY PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `IDENTITY` precompiled contract. +""" +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_IDENTITY, GAS_IDENTITY_WORD, charge_gas + + +def identity(evm: Evm) -> None: + """ + Writes the message data to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_IDENTITY + GAS_IDENTITY_WORD * word_count) + + # OPERATION + evm.output = data diff --git a/src/ethereum/osaka/vm/precompiled_contracts/mapping.py b/src/ethereum/osaka/vm/precompiled_contracts/mapping.py new file mode 100644 index 0000000000..3094d8dff2 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/mapping.py @@ -0,0 +1,74 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Mapping of precompiled contracts their implementations. +""" +from typing import Callable, Dict + +from ...fork_types import Address +from . import ( + ALT_BN128_ADD_ADDRESS, + ALT_BN128_MUL_ADDRESS, + ALT_BN128_PAIRING_CHECK_ADDRESS, + BLAKE2F_ADDRESS, + BLS12_G1_ADD_ADDRESS, + BLS12_G1_MSM_ADDRESS, + BLS12_G2_ADD_ADDRESS, + BLS12_G2_MSM_ADDRESS, + BLS12_MAP_FP2_TO_G2_ADDRESS, + BLS12_MAP_FP_TO_G1_ADDRESS, + BLS12_PAIRING_ADDRESS, + ECRECOVER_ADDRESS, + IDENTITY_ADDRESS, + MODEXP_ADDRESS, + POINT_EVALUATION_ADDRESS, + RIPEMD160_ADDRESS, + SHA256_ADDRESS, +) +from .alt_bn128 import alt_bn128_add, alt_bn128_mul, alt_bn128_pairing_check +from .blake2f import blake2f +from .bls12_381.bls12_381_g1 import ( + bls12_g1_add, + bls12_g1_msm, + bls12_map_fp_to_g1, +) +from .bls12_381.bls12_381_g2 import ( + bls12_g2_add, + bls12_g2_msm, + bls12_map_fp2_to_g2, +) +from .bls12_381.bls12_381_pairing import bls12_pairing +from .ecrecover import ecrecover +from .identity import identity +from .modexp import modexp +from .point_evaluation import point_evaluation +from .ripemd160 import ripemd160 +from .sha256 import sha256 + +PRE_COMPILED_CONTRACTS: Dict[Address, Callable] = { + ECRECOVER_ADDRESS: ecrecover, + SHA256_ADDRESS: sha256, + RIPEMD160_ADDRESS: ripemd160, + IDENTITY_ADDRESS: identity, + MODEXP_ADDRESS: modexp, + ALT_BN128_ADD_ADDRESS: alt_bn128_add, + ALT_BN128_MUL_ADDRESS: alt_bn128_mul, + ALT_BN128_PAIRING_CHECK_ADDRESS: alt_bn128_pairing_check, + BLAKE2F_ADDRESS: blake2f, + POINT_EVALUATION_ADDRESS: point_evaluation, + BLS12_G1_ADD_ADDRESS: bls12_g1_add, + BLS12_G1_MSM_ADDRESS: bls12_g1_msm, + BLS12_G2_ADD_ADDRESS: bls12_g2_add, + BLS12_G2_MSM_ADDRESS: bls12_g2_msm, + BLS12_PAIRING_ADDRESS: bls12_pairing, + BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1, + BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2, +} diff --git a/src/ethereum/osaka/vm/precompiled_contracts/modexp.py b/src/ethereum/osaka/vm/precompiled_contracts/modexp.py new file mode 100644 index 0000000000..403fe86b11 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/modexp.py @@ -0,0 +1,169 @@ +""" +Ethereum Virtual Machine (EVM) MODEXP PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `MODEXP` precompiled contract. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ...vm import Evm +from ...vm.gas import charge_gas +from ..memory import buffer_read + +GQUADDIVISOR = Uint(3) + + +def modexp(evm: Evm) -> None: + """ + Calculates `(base**exp) % modulus` for arbitrary sized `base`, `exp` and. + `modulus`. The return value is the same length as the modulus. + """ + data = evm.message.data + + # GAS + base_length = U256.from_be_bytes(buffer_read(data, U256(0), U256(32))) + exp_length = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + modulus_length = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + + exp_start = U256(96) + base_length + + exp_head = Uint.from_be_bytes( + buffer_read(data, exp_start, min(U256(32), exp_length)) + ) + + charge_gas( + evm, + gas_cost(base_length, modulus_length, exp_length, exp_head), + ) + + # OPERATION + if base_length == 0 and modulus_length == 0: + evm.output = Bytes() + return + + base = Uint.from_be_bytes(buffer_read(data, U256(96), base_length)) + exp = Uint.from_be_bytes(buffer_read(data, exp_start, exp_length)) + + modulus_start = exp_start + exp_length + modulus = Uint.from_be_bytes( + buffer_read(data, modulus_start, modulus_length) + ) + + if modulus == 0: + evm.output = Bytes(b"\x00") * modulus_length + else: + evm.output = pow(base, exp, modulus).to_bytes( + Uint(modulus_length), "big" + ) + + +def complexity(base_length: U256, modulus_length: U256) -> Uint: + """ + Estimate the complexity of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + Returns + ------- + + complexity : `Uint` + Complexity of performing the operation. + """ + max_length = max(Uint(base_length), Uint(modulus_length)) + words = (max_length + Uint(7)) // Uint(8) + return words ** Uint(2) + + +def iterations(exponent_length: U256, exponent_head: Uint) -> Uint: + """ + Calculate the number of iterations required to perform a modular + exponentiation. + + Parameters + ---------- + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + iterations : `Uint` + Number of iterations. + """ + if exponent_length <= U256(32) and exponent_head == U256(0): + count = Uint(0) + elif exponent_length <= U256(32): + bit_length = Uint(exponent_head.bit_length()) + + if bit_length > Uint(0): + bit_length -= Uint(1) + + count = bit_length + else: + length_part = Uint(8) * (Uint(exponent_length) - Uint(32)) + bits_part = Uint(exponent_head.bit_length()) + + if bits_part > Uint(0): + bits_part -= Uint(1) + + count = length_part + bits_part + + return max(count, Uint(1)) + + +def gas_cost( + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent_head: Uint, +) -> Uint: + """ + Calculate the gas cost of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + gas_cost : `Uint` + Gas required for performing the operation. + """ + multiplication_complexity = complexity(base_length, modulus_length) + iteration_count = iterations(exponent_length, exponent_head) + cost = multiplication_complexity * iteration_count + cost //= GQUADDIVISOR + return max(Uint(200), cost) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py b/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py new file mode 100644 index 0000000000..188f90f83f --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py @@ -0,0 +1,72 @@ +""" +Ethereum Virtual Machine (EVM) POINT EVALUATION PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the POINT EVALUATION precompiled contract. +""" +from ethereum_types.bytes import Bytes, Bytes32, Bytes48 +from ethereum_types.numeric import U256 + +from ethereum.crypto.kzg import ( + KZGCommitment, + kzg_commitment_to_versioned_hash, + verify_kzg_proof, +) + +from ...vm import Evm +from ...vm.exceptions import KZGProofError +from ...vm.gas import GAS_POINT_EVALUATION, charge_gas + +FIELD_ELEMENTS_PER_BLOB = 4096 +BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513 # noqa: E501 +VERSIONED_HASH_VERSION_KZG = b"\x01" + + +def point_evaluation(evm: Evm) -> None: + """ + A pre-compile that verifies a KZG proof which claims that a blob + (represented by a commitment) evaluates to a given value at a given point. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + data = evm.message.data + if len(data) != 192: + raise KZGProofError + + versioned_hash = data[:32] + z = Bytes32(data[32:64]) + y = Bytes32(data[64:96]) + commitment = KZGCommitment(data[96:144]) + proof = Bytes48(data[144:192]) + + # GAS + charge_gas(evm, GAS_POINT_EVALUATION) + if kzg_commitment_to_versioned_hash(commitment) != versioned_hash: + raise KZGProofError + + # Verify KZG proof with z and y in big endian format + try: + kzg_proof_verification = verify_kzg_proof(commitment, z, y, proof) + except Exception as e: + raise KZGProofError from e + + if not kzg_proof_verification: + raise KZGProofError + + # Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded + # 32 byte big endian values + evm.output = Bytes( + U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + + U256(BLS_MODULUS).to_be_bytes32() + ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py b/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py new file mode 100644 index 0000000000..6af1086a82 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py @@ -0,0 +1,43 @@ +""" +Ethereum Virtual Machine (EVM) RIPEMD160 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `RIPEMD160` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.byte import left_pad_zero_bytes +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_RIPEMD160, GAS_RIPEMD160_WORD, charge_gas + + +def ripemd160(evm: Evm) -> None: + """ + Writes the ripemd160 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_RIPEMD160 + GAS_RIPEMD160_WORD * word_count) + + # OPERATION + hash_bytes = hashlib.new("ripemd160", data).digest() + padded_hash = left_pad_zero_bytes(hash_bytes, 32) + evm.output = padded_hash diff --git a/src/ethereum/osaka/vm/precompiled_contracts/sha256.py b/src/ethereum/osaka/vm/precompiled_contracts/sha256.py new file mode 100644 index 0000000000..db33a37967 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/sha256.py @@ -0,0 +1,40 @@ +""" +Ethereum Virtual Machine (EVM) SHA256 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `SHA256` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_SHA256, GAS_SHA256_WORD, charge_gas + + +def sha256(evm: Evm) -> None: + """ + Writes the sha256 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_SHA256 + GAS_SHA256_WORD * word_count) + + # OPERATION + evm.output = hashlib.sha256(data).digest() diff --git a/src/ethereum/osaka/vm/runtime.py b/src/ethereum/osaka/vm/runtime.py new file mode 100644 index 0000000000..d6bf90a812 --- /dev/null +++ b/src/ethereum/osaka/vm/runtime.py @@ -0,0 +1,67 @@ +""" +Ethereum Virtual Machine (EVM) Runtime Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Runtime related operations used while executing EVM code. +""" +from typing import Set + +from ethereum_types.numeric import Uint, ulen + +from .instructions import Ops + + +def get_valid_jump_destinations(code: bytes) -> Set[Uint]: + """ + Analyze the evm code to obtain the set of valid jump destinations. + + Valid jump destinations are defined as follows: + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + + Note - Jump destinations are 0-indexed. + + Parameters + ---------- + code : + The EVM code which is to be executed. + + Returns + ------- + valid_jump_destinations: `Set[Uint]` + The set of valid jump destinations in the code. + """ + valid_jump_destinations = set() + pc = Uint(0) + + while pc < ulen(code): + try: + current_opcode = Ops(code[pc]) + except ValueError: + # Skip invalid opcodes, as they don't affect the jumpdest + # analysis. Nevertheless, such invalid opcodes would be caught + # and raised when the interpreter runs. + pc += Uint(1) + continue + + if current_opcode == Ops.JUMPDEST: + valid_jump_destinations.add(pc) + elif Ops.PUSH1.value <= current_opcode.value <= Ops.PUSH32.value: + # If PUSH-N opcodes are encountered, skip the current opcode along + # with the trailing data segment corresponding to the PUSH-N + # opcodes. + push_data_size = current_opcode.value - Ops.PUSH1.value + 1 + pc += Uint(push_data_size) + + pc += Uint(1) + + return valid_jump_destinations diff --git a/src/ethereum/osaka/vm/stack.py b/src/ethereum/osaka/vm/stack.py new file mode 100644 index 0000000000..f28a5b3b88 --- /dev/null +++ b/src/ethereum/osaka/vm/stack.py @@ -0,0 +1,59 @@ +""" +Ethereum Virtual Machine (EVM) Stack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the stack operators for the EVM. +""" + +from typing import List + +from ethereum_types.numeric import U256 + +from .exceptions import StackOverflowError, StackUnderflowError + + +def pop(stack: List[U256]) -> U256: + """ + Pops the top item off of `stack`. + + Parameters + ---------- + stack : + EVM stack. + + Returns + ------- + value : `U256` + The top element on the stack. + + """ + if len(stack) == 0: + raise StackUnderflowError + + return stack.pop() + + +def push(stack: List[U256], value: U256) -> None: + """ + Pushes `value` onto `stack`. + + Parameters + ---------- + stack : + EVM stack. + + value : + Item to be pushed onto `stack`. + + """ + if len(stack) == 1024: + raise StackOverflowError + + return stack.append(value) diff --git a/tests/osaka/__init__.py b/tests/osaka/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/osaka/test_evm_tools.py b/tests/osaka/test_evm_tools.py new file mode 100644 index 0000000000..846a09fd1e --- /dev/null +++ b/tests/osaka/test_evm_tools.py @@ -0,0 +1,66 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH +from tests.helpers.load_evm_tools_tests import ( + fetch_evm_tools_tests, + idfn, + load_evm_tools_test, +) + +ETHEREUM_STATE_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Osaka" + + +SLOW_TESTS = ( + "CALLBlake2f_MaxRounds", + "CALLCODEBlake2f", + "CALLBlake2f", + "loopExp", + "loopMul", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_non-degeneracy-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_bilinearity-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(G1,-G2)=e(-G1,G2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(abG1,G2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(G1,abG2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-inf_pair-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-multi_inf_pair-]", +) + + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_STATE_TESTS_DIR), + ids=idfn, +) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/osaka/test_rlp.py b/tests/osaka/test_rlp.py new file mode 100644 index 0000000000..dd8247a6e2 --- /dev/null +++ b/tests/osaka/test_rlp.py @@ -0,0 +1,167 @@ +import pytest +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.osaka.blocks import Block, Header, Log, Receipt, Withdrawal +from ethereum.osaka.transactions import ( + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, + decode_transaction, + encode_transaction, +) +from ethereum.osaka.utils.hexadecimal import hex_to_address +from ethereum.utils.hexadecimal import hex_to_bytes256 + +hash1 = keccak256(b"foo") +hash2 = keccak256(b"bar") +hash3 = keccak256(b"baz") +hash4 = keccak256(b"foobar") +hash5 = keccak256(b"quux") +hash6 = keccak256(b"foobarbaz") +hash7 = keccak256(b"quuxbaz") + +address1 = hex_to_address("0x00000000219ab540356cbb839cbe05303d7705fa") +address2 = hex_to_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") +address3 = hex_to_address("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8") + +bloom = hex_to_bytes256( + "0x886480c00200620d84180d0470000c503081160044d05015808" + "0037401107060120040105281100100104500414203040a208003" + "4814200610da1208a638d16e440c024880800301e1004c2b02285" + "0602000084c3249a0c084569c90c2002001586241041e8004035a" + "4400a0100938001e041180083180b0340661372060401428c0200" + "87410402b9484028100049481900c08034864314688d001548c30" + "00828e542284180280006402a28a0264da00ac223004006209609" + "83206603200084040122a4739080501251542082020a4087c0002" + "81c08800898d0900024047380000127038098e090801080000429" + "0c84201661040200201c0004b8490ad588804" +) + +legacy_transaction = LegacyTransaction( + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"foo"), + U256(27), + U256(5), + U256(6), +) + +access_list_transaction = AccessListTransaction( + U64(1), + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ((address1, (hash1, hash2)), (address2, tuple())), + U256(27), + U256(5), + U256(6), +) + +transaction_1559 = FeeMarketTransaction( + U64(1), + U256(1), + Uint(7), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ((address1, (hash1, hash2)), (address2, tuple())), + U256(27), + U256(5), + U256(6), +) + +withdrawal = Withdrawal(U64(0), U64(1), address1, U256(2)) + + +header = Header( + parent_hash=hash1, + ommers_hash=hash2, + coinbase=address1, + state_root=hash3, + transactions_root=hash4, + receipt_root=hash5, + bloom=bloom, + difficulty=Uint(1), + number=Uint(2), + gas_limit=Uint(3), + gas_used=Uint(4), + timestamp=U256(5), + extra_data=Bytes(b"foobar"), + prev_randao=Bytes32(b"1234567890abcdef1234567890abcdef"), + nonce=Bytes8(b"12345678"), + base_fee_per_gas=Uint(6), + withdrawals_root=hash6, + parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), + blob_gas_used=U64(7), + excess_blob_gas=U64(8), + requests_hash=hash7, +) + +block = Block( + header=header, + transactions=( + encode_transaction(legacy_transaction), + encode_transaction(access_list_transaction), + encode_transaction(transaction_1559), + ), + ommers=(), + withdrawals=(withdrawal,), +) + +log1 = Log( + address=address1, + topics=(hash1, hash2), + data=Bytes(b"foobar"), +) + +log2 = Log( + address=address1, + topics=(hash1,), + data=Bytes(b"quux"), +) + +receipt = Receipt( + succeeded=True, + cumulative_gas_used=Uint(1), + bloom=bloom, + logs=(log1, log2), +) + + +@pytest.mark.parametrize( + "rlp_object", + [ + legacy_transaction, + access_list_transaction, + transaction_1559, + header, + block, + log1, + log2, + receipt, + withdrawal, + ], +) +def test_cancun_rlp(rlp_object: rlp.Extended) -> None: + encoded = rlp.encode(rlp_object) + assert rlp.decode_to(type(rlp_object), encoded) == rlp_object + + +@pytest.mark.parametrize( + "tx", [legacy_transaction, access_list_transaction, transaction_1559] +) +def test_transaction_encoding(tx: Transaction) -> None: + encoded = encode_transaction(tx) + assert decode_transaction(encoded) == tx diff --git a/tests/osaka/test_state_transition.py b/tests/osaka/test_state_transition.py new file mode 100644 index 0000000000..a543ecea3a --- /dev/null +++ b/tests/osaka/test_state_transition.py @@ -0,0 +1,104 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH +from tests.helpers.load_state_tests import ( + Load, + fetch_state_test_files, + idfn, + run_blockchain_st_test, +) + +ETHEREUM_BLOCKCHAIN_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Osaka" +PACKAGE = "osaka" + +SLOW_TESTS = ( + # GeneralStateTests + "stTimeConsuming/CALLBlake2f_MaxRounds.json", + "stTimeConsuming/static_Call50000_sha256.json", + "vmPerformance/loopExp.json", + "vmPerformance/loopMul.json", + "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Prague", + "CALLBlake2f_d9g0v0_Prague", + "CALLCODEBlake2f_d9g0v0", + # GeneralStateTests + "stRandom/randomStatetest177.json", + "stCreateTest/CreateOOGafterMaxCodesize.json", + # ValidBlockTest + "bcExploitTest/DelegateCallSpam.json", + # InvalidBlockTest + "bcUncleHeaderValidity/nonceWrong.json", + "bcUncleHeaderValidity/wrongMixHash.json", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_non-degeneracy-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_bilinearity-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(G1,-G2\\)=e\\(-G1,G2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(abG1,G2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(G1,abG2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-inf_pair-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-multi_inf_pair-\\]", + "tests/osaka/eip2935_historical_block_hashes_from_state/test_block_hashes\\.py\\:\\:test_block_hashes_history\\[fork_Prague-blockchain_test-full_history_plus_one_check_blockhash_first\\]", +) + +# These are tests that are considered to be incorrect, +# Please provide an explanation when adding entries +IGNORE_TESTS = ( + # ValidBlockTest + "bcForkStressTest/ForkStressTest.json", + "bcGasPricerTest/RPC_API_Test.json", + "bcMultiChainTest", + "bcTotalDifficultyTest", + # InvalidBlockTest + "bcForgedTest", + "bcMultiChainTest", + "GasLimitHigherThan2p63m1_Prague", +) + +# All tests that recursively create a large number of frames (50000) +BIG_MEMORY_TESTS = ( + # GeneralStateTests + "50000_", + "/stQuadraticComplexityTest/", + "/stRandom2/", + "/stRandom/", + "/stSpecialTest/", + "stTimeConsuming/", + "stBadOpcode/", + "stStaticCall/", +) + +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, + ignore_list=IGNORE_TESTS, + slow_list=SLOW_TESTS, + big_memory_list=BIG_MEMORY_TESTS, +) + +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/osaka/test_trie.py b/tests/osaka/test_trie.py new file mode 100644 index 0000000000..04c86a1e08 --- /dev/null +++ b/tests/osaka/test_trie.py @@ -0,0 +1,89 @@ +import json +from typing import Any + +from ethereum.osaka.fork_types import Bytes +from ethereum.osaka.trie import Trie, root, trie_set +from ethereum.utils.hexadecimal import ( + has_hex_prefix, + hex_to_bytes, + remove_hex_prefix, +) +from tests.helpers import TEST_FIXTURES + +FIXTURE_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] + + +def to_bytes(data: str) -> Bytes: + if data is None: + return b"" + if has_hex_prefix(data): + return hex_to_bytes(data) + + return data.encode() + + +def test_trie_secure_hex() -> None: + tests = load_tests("hex_encoded_securetrie_test.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure() -> None: + tests = load_tests("trietest_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure_any_order() -> None: + tests = load_tests("trieanyorder_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie() -> None: + tests = load_tests("trietest.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_any_order() -> None: + tests = load_tests("trieanyorder.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def load_tests(path: str) -> Any: + with open(f"{FIXTURE_PATH}/TrieTests/" + path) as f: + tests = json.load(f) + + return tests From e8155d2d7e090b71602fa41434885413a879e9f5 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Mon, 5 May 2025 23:17:59 -0600 Subject: [PATCH 60/70] Revert "feat: add initial EIP-7805 impl" This reverts commit 7ee273471d3f5295235a2a661ed41251055c883e. --- src/ethereum/prague/blocks.py | 1 - src/ethereum/prague/fork.py | 41 ----------------------------------- 2 files changed, 42 deletions(-) diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index d445fd59c5..80b29087e9 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -82,7 +82,6 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] - inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index bd6f7b2cda..6ab2d1e00e 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -12,7 +12,6 @@ Entry point for the Ethereum specification. """ -from copy import deepcopy from dataclasses import dataclass from typing import List, Optional, Tuple, Union @@ -217,7 +216,6 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, - inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -580,7 +578,6 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], - inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -600,8 +597,6 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. - inclusion_list : - Transactions that must be included in the block if possible. Returns ------- @@ -625,13 +620,6 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) - validate_inclusion_list( - block_env, - block_output, - transactions, - inclusion_list, - ) - process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -877,35 +865,6 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) -def validate_inclusion_list( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, - transactions: Tuple[Union[Bytes, LegacyTransaction], ...], - inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], -) -> None: - """ - Validate the block satisfies the inclusion list. - """ - - for tx in inclusion_list: - # If the transaction is already present in the block, then skip. - if tx in transactions: - continue - - block_env = deepcopy(block_env) - block_output = deepcopy(block_output) - try: - tx = decode_transaction(tx) - index = Uint(len(transactions)) - process_transaction(block_env, block_output, tx, index) - except Exception: - continue - else: - # If the transaction was not in the block and was decoded and - # executed successfully, then mark the block invalid. - raise InvalidBlock - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. From c1d9c2fbb8f0bc609c6f2d72d94ddc240814baa5 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Mon, 5 May 2025 23:29:47 -0600 Subject: [PATCH 61/70] implement EIP-7805 --- src/ethereum/osaka/blocks.py | 1 + src/ethereum/osaka/fork.py | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py index 15208d4ad0..48970bed73 100644 --- a/src/ethereum/osaka/blocks.py +++ b/src/ethereum/osaka/blocks.py @@ -82,6 +82,7 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index 50b5033e53..8d41c428cd 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -12,6 +12,7 @@ Entry point for the Ethereum specification. """ +from copy import deepcopy from dataclasses import dataclass from typing import List, Optional, Tuple, Union @@ -217,6 +218,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, + inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -598,6 +600,7 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], + inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -617,6 +620,8 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. + inclusion_list : + Transactions that must be included in the block if possible. Returns ------- @@ -640,6 +645,13 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) + validate_inclusion_list( + block_env, + block_output, + transactions, + inclusion_list, + ) + process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -888,6 +900,35 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) +def validate_inclusion_list( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + transactions: Tuple[Union[Bytes, LegacyTransaction], ...], + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], +) -> None: + """ + Validate the block satisfies the inclusion list. + """ + + for tx in inclusion_list: + # If the transaction is already present in the block, then skip. + if tx in transactions: + continue + + block_env = deepcopy(block_env) + block_output = deepcopy(block_output) + try: + tx = decode_transaction(tx) + index = Uint(len(transactions)) + process_transaction(block_env, block_output, tx, index) + except Exception: + continue + else: + # If the transaction was not in the block and was decoded and + # executed successfully, then mark the block invalid. + raise InvalidBlock + + def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. From 92c7e87bbebdfaeb72fb51b5201c7c13374e9dd3 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Wed, 14 May 2025 12:47:02 -0600 Subject: [PATCH 62/70] Revert "implement EIP-7805" This reverts commit c1d9c2fbb8f0bc609c6f2d72d94ddc240814baa5. --- src/ethereum/osaka/blocks.py | 1 - src/ethereum/osaka/fork.py | 41 ------------------------------------ 2 files changed, 42 deletions(-) diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py index 48970bed73..15208d4ad0 100644 --- a/src/ethereum/osaka/blocks.py +++ b/src/ethereum/osaka/blocks.py @@ -82,7 +82,6 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] - inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index 8d41c428cd..50b5033e53 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -12,7 +12,6 @@ Entry point for the Ethereum specification. """ -from copy import deepcopy from dataclasses import dataclass from typing import List, Optional, Tuple, Union @@ -218,7 +217,6 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, - inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -600,7 +598,6 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], - inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -620,8 +617,6 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. - inclusion_list : - Transactions that must be included in the block if possible. Returns ------- @@ -645,13 +640,6 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) - validate_inclusion_list( - block_env, - block_output, - transactions, - inclusion_list, - ) - process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -900,35 +888,6 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) -def validate_inclusion_list( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, - transactions: Tuple[Union[Bytes, LegacyTransaction], ...], - inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], -) -> None: - """ - Validate the block satisfies the inclusion list. - """ - - for tx in inclusion_list: - # If the transaction is already present in the block, then skip. - if tx in transactions: - continue - - block_env = deepcopy(block_env) - block_output = deepcopy(block_output) - try: - tx = decode_transaction(tx) - index = Uint(len(transactions)) - process_transaction(block_env, block_output, tx, index) - except Exception: - continue - else: - # If the transaction was not in the block and was decoded and - # executed successfully, then mark the block invalid. - raise InvalidBlock - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. From 4a783748bbf88a6478163deb99e0ec5a5b9fe0cb Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Wed, 14 May 2025 12:47:13 -0600 Subject: [PATCH 63/70] Revert "osaka initial commit" This reverts commit 4efe7640da7cb1a8746477151e9d6a01608504c9. --- src/ethereum/osaka/__init__.py | 7 - src/ethereum/osaka/blocks.py | 136 --- src/ethereum/osaka/bloom.py | 85 -- src/ethereum/osaka/exceptions.py | 24 - src/ethereum/osaka/fork.py | 962 ------------------ src/ethereum/osaka/fork_types.py | 79 -- src/ethereum/osaka/requests.py | 74 -- src/ethereum/osaka/state.py | 720 ------------- src/ethereum/osaka/transactions.py | 579 ----------- src/ethereum/osaka/trie.py | 494 --------- src/ethereum/osaka/utils/__init__.py | 3 - src/ethereum/osaka/utils/address.py | 93 -- src/ethereum/osaka/utils/hexadecimal.py | 68 -- src/ethereum/osaka/utils/message.py | 95 -- src/ethereum/osaka/vm/__init__.py | 209 ---- src/ethereum/osaka/vm/eoa_delegation.py | 214 ---- src/ethereum/osaka/vm/exceptions.py | 140 --- src/ethereum/osaka/vm/gas.py | 369 ------- .../osaka/vm/instructions/__init__.py | 366 ------- .../osaka/vm/instructions/arithmetic.py | 373 ------- src/ethereum/osaka/vm/instructions/bitwise.py | 240 ----- src/ethereum/osaka/vm/instructions/block.py | 255 ----- .../osaka/vm/instructions/comparison.py | 178 ---- .../osaka/vm/instructions/control_flow.py | 171 ---- .../osaka/vm/instructions/environment.py | 597 ----------- src/ethereum/osaka/vm/instructions/keccak.py | 64 -- src/ethereum/osaka/vm/instructions/log.py | 88 -- src/ethereum/osaka/vm/instructions/memory.py | 177 ---- src/ethereum/osaka/vm/instructions/stack.py | 209 ---- src/ethereum/osaka/vm/instructions/storage.py | 184 ---- src/ethereum/osaka/vm/instructions/system.py | 755 -------------- src/ethereum/osaka/vm/interpreter.py | 328 ------ src/ethereum/osaka/vm/memory.py | 81 -- .../vm/precompiled_contracts/__init__.py | 54 - .../vm/precompiled_contracts/alt_bn128.py | 154 --- .../osaka/vm/precompiled_contracts/blake2f.py | 41 - .../bls12_381/__init__.py | 583 ----------- .../bls12_381/bls12_381_g1.py | 148 --- .../bls12_381/bls12_381_g2.py | 148 --- .../bls12_381/bls12_381_pairing.py | 67 -- .../vm/precompiled_contracts/ecrecover.py | 63 -- .../vm/precompiled_contracts/identity.py | 38 - .../osaka/vm/precompiled_contracts/mapping.py | 74 -- .../osaka/vm/precompiled_contracts/modexp.py | 169 --- .../precompiled_contracts/point_evaluation.py | 72 -- .../vm/precompiled_contracts/ripemd160.py | 43 - .../osaka/vm/precompiled_contracts/sha256.py | 40 - src/ethereum/osaka/vm/runtime.py | 67 -- src/ethereum/osaka/vm/stack.py | 59 -- tests/osaka/__init__.py | 0 tests/osaka/test_evm_tools.py | 66 -- tests/osaka/test_rlp.py | 167 --- tests/osaka/test_state_transition.py | 104 -- tests/osaka/test_trie.py | 89 -- 54 files changed, 10663 deletions(-) delete mode 100644 src/ethereum/osaka/__init__.py delete mode 100644 src/ethereum/osaka/blocks.py delete mode 100644 src/ethereum/osaka/bloom.py delete mode 100644 src/ethereum/osaka/exceptions.py delete mode 100644 src/ethereum/osaka/fork.py delete mode 100644 src/ethereum/osaka/fork_types.py delete mode 100644 src/ethereum/osaka/requests.py delete mode 100644 src/ethereum/osaka/state.py delete mode 100644 src/ethereum/osaka/transactions.py delete mode 100644 src/ethereum/osaka/trie.py delete mode 100644 src/ethereum/osaka/utils/__init__.py delete mode 100644 src/ethereum/osaka/utils/address.py delete mode 100644 src/ethereum/osaka/utils/hexadecimal.py delete mode 100644 src/ethereum/osaka/utils/message.py delete mode 100644 src/ethereum/osaka/vm/__init__.py delete mode 100644 src/ethereum/osaka/vm/eoa_delegation.py delete mode 100644 src/ethereum/osaka/vm/exceptions.py delete mode 100644 src/ethereum/osaka/vm/gas.py delete mode 100644 src/ethereum/osaka/vm/instructions/__init__.py delete mode 100644 src/ethereum/osaka/vm/instructions/arithmetic.py delete mode 100644 src/ethereum/osaka/vm/instructions/bitwise.py delete mode 100644 src/ethereum/osaka/vm/instructions/block.py delete mode 100644 src/ethereum/osaka/vm/instructions/comparison.py delete mode 100644 src/ethereum/osaka/vm/instructions/control_flow.py delete mode 100644 src/ethereum/osaka/vm/instructions/environment.py delete mode 100644 src/ethereum/osaka/vm/instructions/keccak.py delete mode 100644 src/ethereum/osaka/vm/instructions/log.py delete mode 100644 src/ethereum/osaka/vm/instructions/memory.py delete mode 100644 src/ethereum/osaka/vm/instructions/stack.py delete mode 100644 src/ethereum/osaka/vm/instructions/storage.py delete mode 100644 src/ethereum/osaka/vm/instructions/system.py delete mode 100644 src/ethereum/osaka/vm/interpreter.py delete mode 100644 src/ethereum/osaka/vm/memory.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/__init__.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/blake2f.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/identity.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/mapping.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/modexp.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py delete mode 100644 src/ethereum/osaka/vm/precompiled_contracts/sha256.py delete mode 100644 src/ethereum/osaka/vm/runtime.py delete mode 100644 src/ethereum/osaka/vm/stack.py delete mode 100644 tests/osaka/__init__.py delete mode 100644 tests/osaka/test_evm_tools.py delete mode 100644 tests/osaka/test_rlp.py delete mode 100644 tests/osaka/test_state_transition.py delete mode 100644 tests/osaka/test_trie.py diff --git a/src/ethereum/osaka/__init__.py b/src/ethereum/osaka/__init__.py deleted file mode 100644 index 8c5c37298a..0000000000 --- a/src/ethereum/osaka/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -The Osaka fork. -""" - -from ethereum.fork_criteria import Unscheduled - -FORK_CRITERIA = Unscheduled() diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py deleted file mode 100644 index 15208d4ad0..0000000000 --- a/src/ethereum/osaka/blocks.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -A `Block` is a single link in the chain that is Ethereum. Each `Block` contains -a `Header` and zero or more transactions. Each `Header` contains associated -metadata like the block number, parent block hash, and how much gas was -consumed by its transactions. - -Together, these blocks form a cryptographically secure journal recording the -history of all state transitions that have happened since the genesis of the -chain. -""" -from dataclasses import dataclass -from typing import Tuple, Union - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes8, Bytes32 -from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint - -from ..crypto.hash import Hash32 -from .fork_types import Address, Bloom, Root -from .transactions import ( - AccessListTransaction, - BlobTransaction, - FeeMarketTransaction, - LegacyTransaction, - SetCodeTransaction, - Transaction, -) - - -@slotted_freezable -@dataclass -class Withdrawal: - """ - Withdrawals that have been validated on the consensus layer. - """ - - index: U64 - validator_index: U64 - address: Address - amount: U256 - - -@slotted_freezable -@dataclass -class Header: - """ - Header portion of a block on the chain. - """ - - parent_hash: Hash32 - ommers_hash: Hash32 - coinbase: Address - state_root: Root - transactions_root: Root - receipt_root: Root - bloom: Bloom - difficulty: Uint - number: Uint - gas_limit: Uint - gas_used: Uint - timestamp: U256 - extra_data: Bytes - prev_randao: Bytes32 - nonce: Bytes8 - base_fee_per_gas: Uint - withdrawals_root: Root - blob_gas_used: U64 - excess_blob_gas: U64 - parent_beacon_block_root: Root - requests_hash: Hash32 - - -@slotted_freezable -@dataclass -class Block: - """ - A complete block. - """ - - header: Header - transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] - withdrawals: Tuple[Withdrawal, ...] - - -@slotted_freezable -@dataclass -class Log: - """ - Data record produced during the execution of a transaction. - """ - - address: Address - topics: Tuple[Hash32, ...] - data: bytes - - -@slotted_freezable -@dataclass -class Receipt: - """ - Result of a transaction. - """ - - succeeded: bool - cumulative_gas_used: Uint - bloom: Bloom - logs: Tuple[Log, ...] - - -def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: - """ - Encodes a receipt. - """ - if isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(receipt) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(receipt) - elif isinstance(tx, BlobTransaction): - return b"\x03" + rlp.encode(receipt) - elif isinstance(tx, SetCodeTransaction): - return b"\x04" + rlp.encode(receipt) - else: - return receipt - - -def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: - """ - Decodes a receipt. - """ - if isinstance(receipt, Bytes): - assert receipt[0] in (1, 2, 3, 4) - return rlp.decode_to(Receipt, receipt[1:]) - else: - return receipt diff --git a/src/ethereum/osaka/bloom.py b/src/ethereum/osaka/bloom.py deleted file mode 100644 index 0ba6e431ab..0000000000 --- a/src/ethereum/osaka/bloom.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Ethereum Logs Bloom -^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -This modules defines functions for calculating bloom filters of logs. For the -general theory of bloom filters see e.g. `Wikipedia -`_. Bloom filters are used to allow -for efficient searching of logs by address and/or topic, by rapidly -eliminating blocks and receipts from their search. -""" - -from typing import Tuple - -from ethereum_types.numeric import Uint - -from ethereum.crypto.hash import keccak256 - -from .blocks import Log -from .fork_types import Bloom - - -def add_to_bloom(bloom: bytearray, bloom_entry: bytes) -> None: - """ - Add a bloom entry to the bloom filter (`bloom`). - - The number of hash functions used is 3. They are calculated by taking the - least significant 11 bits from the first 3 16-bit words of the - `keccak_256()` hash of `bloom_entry`. - - Parameters - ---------- - bloom : - The bloom filter. - bloom_entry : - An entry which is to be added to bloom filter. - """ - hash = keccak256(bloom_entry) - - for idx in (0, 2, 4): - # Obtain the least significant 11 bits from the pair of bytes - # (16 bits), and set this bit in bloom bytearray. - # The obtained bit is 0-indexed in the bloom filter from the least - # significant bit to the most significant bit. - bit_to_set = Uint.from_be_bytes(hash[idx : idx + 2]) & Uint(0x07FF) - # Below is the index of the bit in the bytearray (where 0-indexed - # byte is the most significant byte) - bit_index = 0x07FF - int(bit_to_set) - - byte_index = bit_index // 8 - bit_value = 1 << (7 - (bit_index % 8)) - bloom[byte_index] = bloom[byte_index] | bit_value - - -def logs_bloom(logs: Tuple[Log, ...]) -> Bloom: - """ - Obtain the logs bloom from a list of log entries. - - The address and each topic of a log are added to the bloom filter. - - Parameters - ---------- - logs : - List of logs for which the logs bloom is to be obtained. - - Returns - ------- - logs_bloom : `Bloom` - The logs bloom obtained which is 256 bytes with some bits set as per - the caller address and the log topics. - """ - bloom: bytearray = bytearray(b"\x00" * 256) - - for log in logs: - add_to_bloom(bloom, log.address) - for topic in log.topics: - add_to_bloom(bloom, topic) - - return Bloom(bloom) diff --git a/src/ethereum/osaka/exceptions.py b/src/ethereum/osaka/exceptions.py deleted file mode 100644 index 5781a2c1c3..0000000000 --- a/src/ethereum/osaka/exceptions.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Exceptions specific to this fork. -""" - -from typing import Final - -from ethereum.exceptions import InvalidTransaction - - -class TransactionTypeError(InvalidTransaction): - """ - Unknown [EIP-2718] transaction type byte. - - [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 - """ - - transaction_type: Final[int] - """ - The type byte of the transaction that caused the error. - """ - - def __init__(self, transaction_type: int): - super().__init__(f"unknown transaction type `{transaction_type}`") - self.transaction_type = transaction_type diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py deleted file mode 100644 index 50b5033e53..0000000000 --- a/src/ethereum/osaka/fork.py +++ /dev/null @@ -1,962 +0,0 @@ -""" -Ethereum Specification -^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Entry point for the Ethereum specification. -""" - -from dataclasses import dataclass -from typing import List, Optional, Tuple, Union - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import ( - EthereumException, - InvalidBlock, - InvalidSenderError, -) - -from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt -from .bloom import logs_bloom -from .fork_types import Account, Address, Authorization, VersionedHash -from .requests import ( - CONSOLIDATION_REQUEST_TYPE, - DEPOSIT_REQUEST_TYPE, - WITHDRAWAL_REQUEST_TYPE, - compute_requests_hash, - parse_deposit_requests_from_receipt, -) -from .state import ( - State, - TransientStorage, - account_exists_and_is_empty, - destroy_account, - destroy_touched_empty_accounts, - get_account, - increment_nonce, - modify_state, - set_account_balance, - state_root, -) -from .transactions import ( - AccessListTransaction, - BlobTransaction, - FeeMarketTransaction, - LegacyTransaction, - SetCodeTransaction, - Transaction, - decode_transaction, - encode_transaction, - get_transaction_hash, - recover_sender, - validate_transaction, -) -from .trie import root, trie_set -from .utils.hexadecimal import hex_to_address -from .utils.message import prepare_message -from .vm import Message -from .vm.eoa_delegation import is_valid_delegation -from .vm.gas import ( - calculate_blob_gas_price, - calculate_data_fee, - calculate_excess_blob_gas, - calculate_total_blob_gas, -) -from .vm.interpreter import MessageCallOutput, process_message_call - -BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) -ELASTICITY_MULTIPLIER = Uint(2) -GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) -GAS_LIMIT_MINIMUM = Uint(5000) -EMPTY_OMMER_HASH = keccak256(rlp.encode([])) -SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") -BEACON_ROOTS_ADDRESS = hex_to_address( - "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" -) -SYSTEM_TRANSACTION_GAS = Uint(30000000) -MAX_BLOB_GAS_PER_BLOCK = Uint(1179648) -VERSIONED_HASH_VERSION_KZG = b"\x01" - -WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x00000961Ef480Eb55e80D19ad83579A64c007002" -) -CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x0000BBdDc7CE488642fb579F8B00f3a590007251" -) -HISTORY_STORAGE_ADDRESS = hex_to_address( - "0x0000F90827F1C53a10cb7A02335B175320002935" -) -HISTORY_SERVE_WINDOW = 8192 - - -@dataclass -class BlockChain: - """ - History and current state of the block chain. - """ - - blocks: List[Block] - state: State - chain_id: U64 - - -def apply_fork(old: BlockChain) -> BlockChain: - """ - Transforms the state from the previous hard fork (`old`) into the block - chain object for this hard fork and returns it. - - When forks need to implement an irregular state transition, this function - is used to handle the irregularity. See the :ref:`DAO Fork ` for - an example. - - Parameters - ---------- - old : - Previous block chain object. - - Returns - ------- - new : `BlockChain` - Upgraded block chain object for this hard fork. - """ - return old - - -def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: - """ - Obtain the list of hashes of the previous 256 blocks in order of - increasing block number. - - This function will return less hashes for the first 256 blocks. - - The ``BLOCKHASH`` opcode needs to access the latest hashes on the chain, - therefore this function retrieves them. - - Parameters - ---------- - chain : - History and current state. - - Returns - ------- - recent_block_hashes : `List[Hash32]` - Hashes of the recent 256 blocks in order of increasing block number. - """ - recent_blocks = chain.blocks[-255:] - # TODO: This function has not been tested rigorously - if len(recent_blocks) == 0: - return [] - - recent_block_hashes = [] - - for block in recent_blocks: - prev_block_hash = block.header.parent_hash - recent_block_hashes.append(prev_block_hash) - - # We are computing the hash only for the most recent block and not for - # the rest of the blocks as they have successors which have the hash of - # the current block as parent hash. - most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header)) - recent_block_hashes.append(most_recent_block_hash) - - return recent_block_hashes - - -def state_transition(chain: BlockChain, block: Block) -> None: - """ - Attempts to apply a block to an existing block chain. - - All parts of the block's contents need to be verified before being added - to the chain. Blocks are verified by ensuring that the contents of the - block make logical sense with the contents of the parent block. The - information in the block's header must also match the corresponding - information in the block. - - To implement Ethereum, in theory clients are only required to store the - most recent 255 blocks of the chain since as far as execution is - concerned, only those blocks are accessed. Practically, however, clients - should store more blocks to handle reorgs. - - Parameters - ---------- - chain : - History and current state. - block : - Block to apply to `chain`. - """ - validate_header(chain, block.header) - if block.ommers != (): - raise InvalidBlock - - block_env = vm.BlockEnvironment( - chain_id=chain.chain_id, - state=chain.state, - block_gas_limit=block.header.gas_limit, - block_hashes=get_last_256_block_hashes(chain), - coinbase=block.header.coinbase, - number=block.header.number, - base_fee_per_gas=block.header.base_fee_per_gas, - time=block.header.timestamp, - prev_randao=block.header.prev_randao, - excess_blob_gas=block.header.excess_blob_gas, - parent_beacon_block_root=block.header.parent_beacon_block_root, - ) - - block_output = apply_body( - block_env=block_env, - transactions=block.transactions, - withdrawals=block.withdrawals, - ) - block_state_root = state_root(block_env.state) - transactions_root = root(block_output.transactions_trie) - receipt_root = root(block_output.receipts_trie) - block_logs_bloom = logs_bloom(block_output.block_logs) - withdrawals_root = root(block_output.withdrawals_trie) - requests_hash = compute_requests_hash(block_output.requests) - - if block_output.block_gas_used != block.header.gas_used: - raise InvalidBlock( - f"{block_output.block_gas_used} != {block.header.gas_used}" - ) - if transactions_root != block.header.transactions_root: - raise InvalidBlock - if block_state_root != block.header.state_root: - raise InvalidBlock - if receipt_root != block.header.receipt_root: - raise InvalidBlock - if block_logs_bloom != block.header.bloom: - raise InvalidBlock - if withdrawals_root != block.header.withdrawals_root: - raise InvalidBlock - if block_output.blob_gas_used != block.header.blob_gas_used: - raise InvalidBlock - if requests_hash != block.header.requests_hash: - raise InvalidBlock - - chain.blocks.append(block) - if len(chain.blocks) > 255: - # Real clients have to store more blocks to deal with reorgs, but the - # protocol only requires the last 255 - chain.blocks = chain.blocks[-255:] - - -def calculate_base_fee_per_gas( - block_gas_limit: Uint, - parent_gas_limit: Uint, - parent_gas_used: Uint, - parent_base_fee_per_gas: Uint, -) -> Uint: - """ - Calculates the base fee per gas for the block. - - Parameters - ---------- - block_gas_limit : - Gas limit of the block for which the base fee is being calculated. - parent_gas_limit : - Gas limit of the parent block. - parent_gas_used : - Gas used in the parent block. - parent_base_fee_per_gas : - Base fee per gas of the parent block. - - Returns - ------- - base_fee_per_gas : `Uint` - Base fee per gas for the block. - """ - parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER - if not check_gas_limit(block_gas_limit, parent_gas_limit): - raise InvalidBlock - - if parent_gas_used == parent_gas_target: - expected_base_fee_per_gas = parent_base_fee_per_gas - elif parent_gas_used > parent_gas_target: - gas_used_delta = parent_gas_used - parent_gas_target - - parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta - target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target - - base_fee_per_gas_delta = max( - target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR, - Uint(1), - ) - - expected_base_fee_per_gas = ( - parent_base_fee_per_gas + base_fee_per_gas_delta - ) - else: - gas_used_delta = parent_gas_target - parent_gas_used - - parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta - target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target - - base_fee_per_gas_delta = ( - target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR - ) - - expected_base_fee_per_gas = ( - parent_base_fee_per_gas - base_fee_per_gas_delta - ) - - return Uint(expected_base_fee_per_gas) - - -def validate_header(chain: BlockChain, header: Header) -> None: - """ - Verifies a block header. - - In order to consider a block's header valid, the logic for the - quantities in the header should match the logic for the block itself. - For example the header timestamp should be greater than the block's parent - timestamp because the block was created *after* the parent block. - Additionally, the block's number should be directly following the parent - block's number since it is the next block in the sequence. - - Parameters - ---------- - chain : - History and current state. - header : - Header to check for correctness. - """ - if header.number < Uint(1): - raise InvalidBlock - parent_header_number = header.number - Uint(1) - first_block_number = chain.blocks[0].header.number - last_block_number = chain.blocks[-1].header.number - - if ( - parent_header_number < first_block_number - or parent_header_number > last_block_number - ): - raise InvalidBlock - - parent_header = chain.blocks[ - parent_header_number - first_block_number - ].header - - excess_blob_gas = calculate_excess_blob_gas(parent_header) - if header.excess_blob_gas != excess_blob_gas: - raise InvalidBlock - - if header.gas_used > header.gas_limit: - raise InvalidBlock - - expected_base_fee_per_gas = calculate_base_fee_per_gas( - header.gas_limit, - parent_header.gas_limit, - parent_header.gas_used, - parent_header.base_fee_per_gas, - ) - if expected_base_fee_per_gas != header.base_fee_per_gas: - raise InvalidBlock - if header.timestamp <= parent_header.timestamp: - raise InvalidBlock - if header.number != parent_header.number + Uint(1): - raise InvalidBlock - if len(header.extra_data) > 32: - raise InvalidBlock - if header.difficulty != 0: - raise InvalidBlock - if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00": - raise InvalidBlock - if header.ommers_hash != EMPTY_OMMER_HASH: - raise InvalidBlock - - block_parent_hash = keccak256(rlp.encode(parent_header)) - if header.parent_hash != block_parent_hash: - raise InvalidBlock - - -def check_transaction( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, - tx: Transaction, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], Uint]: - """ - Check if the transaction is includable in the block. - - Parameters - ---------- - block_env : - The block scoped environment. - block_output : - The block output for the current block. - tx : - The transaction. - - Returns - ------- - sender_address : - The sender of the transaction. - effective_gas_price : - The price to charge for gas when the transaction is executed. - blob_versioned_hashes : - The blob versioned hashes of the transaction. - tx_blob_gas_used: - The blob gas used by the transaction. - - Raises - ------ - InvalidBlock : - If the transaction is not includable. - """ - gas_available = block_env.block_gas_limit - block_output.block_gas_used - blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used - - if tx.gas > gas_available: - raise InvalidBlock - - tx_blob_gas_used = calculate_total_blob_gas(tx) - if tx_blob_gas_used > blob_gas_available: - raise InvalidBlock - - sender_address = recover_sender(block_env.chain_id, tx) - sender_account = get_account(block_env.state, sender_address) - - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise InvalidBlock - if tx.max_fee_per_gas < block_env.base_fee_per_gas: - raise InvalidBlock - - priority_fee_per_gas = min( - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - block_env.base_fee_per_gas, - ) - effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas - max_gas_fee = tx.gas * tx.max_fee_per_gas - else: - if tx.gas_price < block_env.base_fee_per_gas: - raise InvalidBlock - effective_gas_price = tx.gas_price - max_gas_fee = tx.gas * tx.gas_price - - if isinstance(tx, BlobTransaction): - if len(tx.blob_versioned_hashes) == 0: - raise InvalidBlock - for blob_versioned_hash in tx.blob_versioned_hashes: - if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: - raise InvalidBlock - - blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) - if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: - raise InvalidBlock - - max_gas_fee += calculate_total_blob_gas(tx) * Uint( - tx.max_fee_per_blob_gas - ) - blob_versioned_hashes = tx.blob_versioned_hashes - else: - blob_versioned_hashes = () - - if isinstance(tx, (BlobTransaction, SetCodeTransaction)): - if not isinstance(tx.to, Address): - raise InvalidBlock - - if isinstance(tx, SetCodeTransaction): - if not any(tx.authorizations): - raise InvalidBlock - - if sender_account.nonce != tx.nonce: - raise InvalidBlock - if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): - raise InvalidBlock - if sender_account.code and not is_valid_delegation(sender_account.code): - raise InvalidSenderError("not EOA") - - return ( - sender_address, - effective_gas_price, - blob_versioned_hashes, - tx_blob_gas_used, - ) - - -def make_receipt( - tx: Transaction, - error: Optional[EthereumException], - cumulative_gas_used: Uint, - logs: Tuple[Log, ...], -) -> Union[Bytes, Receipt]: - """ - Make the receipt for a transaction that was executed. - - Parameters - ---------- - tx : - The executed transaction. - error : - Error in the top level frame of the transaction, if any. - cumulative_gas_used : - The total gas used so far in the block after the transaction was - executed. - logs : - The logs produced by the transaction. - - Returns - ------- - receipt : - The receipt for the transaction. - """ - receipt = Receipt( - succeeded=error is None, - cumulative_gas_used=cumulative_gas_used, - bloom=logs_bloom(logs), - logs=logs, - ) - - return encode_receipt(tx, receipt) - - -def process_system_transaction( - block_env: vm.BlockEnvironment, - target_address: Address, - data: Bytes, -) -> MessageCallOutput: - """ - Process a system transaction. - - Parameters - ---------- - block_env : - The block scoped environment. - target_address : - Address of the contract to call. - data : - Data to pass to the contract. - - Returns - ------- - system_tx_output : `MessageCallOutput` - Output of processing the system transaction. - """ - system_contract_code = get_account(block_env.state, target_address).code - - tx_env = vm.TransactionEnvironment( - origin=SYSTEM_ADDRESS, - gas_price=block_env.base_fee_per_gas, - gas=SYSTEM_TRANSACTION_GAS, - access_list_addresses=set(), - access_list_storage_keys=set(), - transient_storage=TransientStorage(), - blob_versioned_hashes=(), - authorizations=(), - index_in_block=None, - tx_hash=None, - traces=[], - ) - - system_tx_message = Message( - block_env=block_env, - tx_env=tx_env, - caller=SYSTEM_ADDRESS, - target=target_address, - gas=SYSTEM_TRANSACTION_GAS, - value=U256(0), - data=data, - code=system_contract_code, - depth=Uint(0), - current_target=target_address, - code_address=target_address, - should_transfer_value=False, - is_static=False, - accessed_addresses=set(), - accessed_storage_keys=set(), - parent_evm=None, - ) - - system_tx_output = process_message_call(system_tx_message) - - # TODO: Empty accounts in post-merge forks are impossible - # see Ethereum Improvement Proposal 7523. - # This line is only included to support invalid tests in the test suite - # and will have to be removed in the future. - # See https://github.com/ethereum/execution-specs/issues/955 - destroy_touched_empty_accounts( - block_env.state, system_tx_output.touched_accounts - ) - - return system_tx_output - - -def apply_body( - block_env: vm.BlockEnvironment, - transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - withdrawals: Tuple[Withdrawal, ...], -) -> vm.BlockOutput: - """ - Executes a block. - - Many of the contents of a block are stored in data structures called - tries. There is a transactions trie which is similar to a ledger of the - transactions stored in the current block. There is also a receipts trie - which stores the results of executing a transaction, like the post state - and gas used. This function creates and executes the block that is to be - added to the chain. - - Parameters - ---------- - block_env : - The block scoped environment. - transactions : - Transactions included in the block. - withdrawals : - Withdrawals to be processed in the current block. - - Returns - ------- - block_output : - The block output for the current block. - """ - block_output = vm.BlockOutput() - - process_system_transaction( - block_env=block_env, - target_address=BEACON_ROOTS_ADDRESS, - data=block_env.parent_beacon_block_root, - ) - - process_system_transaction( - block_env=block_env, - target_address=HISTORY_STORAGE_ADDRESS, - data=block_env.block_hashes[-1], # The parent hash - ) - - for i, tx in enumerate(map(decode_transaction, transactions)): - process_transaction(block_env, block_output, tx, Uint(i)) - - process_withdrawals(block_env, block_output, withdrawals) - - process_general_purpose_requests( - block_env=block_env, - block_output=block_output, - ) - - return block_output - - -def process_general_purpose_requests( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, -) -> None: - """ - Process all the requests in the block. - - Parameters - ---------- - block_env : - The execution environment for the Block. - block_output : - The block output for the current block. - """ - # Requests are to be in ascending order of request type - deposit_requests = block_output.deposit_requests - requests_from_execution = block_output.requests - if len(deposit_requests) > 0: - requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) - - system_withdrawal_tx_output = process_system_transaction( - block_env=block_env, - target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - data=b"", - ) - - if len(system_withdrawal_tx_output.return_data) > 0: - requests_from_execution.append( - WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data - ) - - system_consolidation_tx_output = process_system_transaction( - block_env=block_env, - target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, - data=b"", - ) - - if len(system_consolidation_tx_output.return_data) > 0: - requests_from_execution.append( - CONSOLIDATION_REQUEST_TYPE - + system_consolidation_tx_output.return_data - ) - - -def process_transaction( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, - tx: Transaction, - index: Uint, -) -> None: - """ - Execute a transaction against the provided environment. - - This function processes the actions needed to execute a transaction. - It decrements the sender's account after calculating the gas fee and - refunds them the proper amount after execution. Calling contracts, - deploying code, and incrementing nonces are all examples of actions that - happen within this function or from a call made within this function. - - Accounts that are marked for deletion are processed and destroyed after - execution. - - Parameters - ---------- - block_env : - Environment for the Ethereum Virtual Machine. - block_output : - The block output for the current block. - tx : - Transaction to execute. - index: - Index of the transaction in the block. - """ - trie_set( - block_output.transactions_trie, - rlp.encode(index), - encode_transaction(tx), - ) - - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) - - ( - sender, - effective_gas_price, - blob_versioned_hashes, - tx_blob_gas_used, - ) = check_transaction( - block_env=block_env, - block_output=block_output, - tx=tx, - ) - - sender_account = get_account(block_env.state, sender) - - if isinstance(tx, BlobTransaction): - blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) - else: - blob_gas_fee = Uint(0) - - effective_gas_fee = tx.gas * effective_gas_price - - gas = tx.gas - intrinsic_gas - increment_nonce(block_env.state, 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) - ) - - access_list_addresses = set() - access_list_storage_keys = set() - access_list_addresses.add(block_env.coinbase) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): - for address, keys in tx.access_list: - access_list_addresses.add(address) - for key in keys: - access_list_storage_keys.add((address, key)) - - authorizations: Tuple[Authorization, ...] = () - if isinstance(tx, SetCodeTransaction): - authorizations = tx.authorizations - - tx_env = vm.TransactionEnvironment( - origin=sender, - gas_price=effective_gas_price, - gas=gas, - access_list_addresses=access_list_addresses, - access_list_storage_keys=access_list_storage_keys, - transient_storage=TransientStorage(), - blob_versioned_hashes=blob_versioned_hashes, - authorizations=authorizations, - index_in_block=index, - tx_hash=get_transaction_hash(encode_transaction(tx)), - traces=[], - ) - - message = prepare_message(block_env, tx_env, tx) - - tx_output = process_message_call(message) - - # For EIP-7623 we first calculate the execution_gas_used, which includes - # the execution gas refund. - execution_gas_used = tx.gas - tx_output.gas_left - gas_refund = min( - execution_gas_used // Uint(5), Uint(tx_output.refund_counter) - ) - execution_gas_used -= gas_refund - - # Transactions with less execution_gas_used than the floor pay at the - # floor cost. - tx_gas_used = max(execution_gas_used, calldata_floor_gas_cost) - - tx_output.gas_left = tx.gas - tx_gas_used - gas_refund_amount = tx_output.gas_left * effective_gas_price - - # For non-1559 transactions effective_gas_price == tx.gas_price - priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas - transaction_fee = tx_gas_used * 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) - - # transfer miner fees - coinbase_balance_after_mining_fee = get_account( - block_env.state, block_env.coinbase - ).balance + U256(transaction_fee) - if coinbase_balance_after_mining_fee != 0: - set_account_balance( - block_env.state, - 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) - - for address in tx_output.accounts_to_delete: - destroy_account(block_env.state, address) - - destroy_touched_empty_accounts(block_env.state, tx_output.touched_accounts) - - block_output.block_gas_used += tx_gas_used - block_output.blob_gas_used += tx_blob_gas_used - - receipt = make_receipt( - tx, tx_output.error, block_output.block_gas_used, tx_output.logs - ) - - trie_set( - block_output.receipts_trie, - rlp.encode(Uint(index)), - receipt, - ) - - block_output.block_logs += tx_output.logs - - block_output.deposit_requests += parse_deposit_requests_from_receipt( - receipt - ) - - -def process_withdrawals( - block_env: vm.BlockEnvironment, - block_output: vm.BlockOutput, - withdrawals: Tuple[Withdrawal, ...], -) -> None: - """ - Increase the balance of the withdrawing account. - """ - - def increase_recipient_balance(recipient: Account) -> None: - recipient.balance += wd.amount * U256(10**9) - - for i, wd in enumerate(withdrawals): - trie_set( - block_output.withdrawals_trie, - rlp.encode(Uint(i)), - rlp.encode(wd), - ) - - modify_state(block_env.state, wd.address, increase_recipient_balance) - - if account_exists_and_is_empty(block_env.state, wd.address): - destroy_account(block_env.state, wd.address) - - -def compute_header_hash(header: Header) -> Hash32: - """ - Computes the hash of a block header. - - The header hash of a block is the canonical hash that is used to refer - to a specific block and completely distinguishes a block from another. - - ``keccak256`` is a function that produces a 256 bit hash of any input. - It also takes in any number of bytes as an input and produces a single - hash for them. A hash is a completely unique output for a single input. - So an input corresponds to one unique hash that can be used to identify - the input exactly. - - Prior to using the ``keccak256`` hash function, the header must be - encoded using the Recursive-Length Prefix. See :ref:`rlp`. - RLP encoding the header converts it into a space-efficient format that - allows for easy transfer of data between nodes. The purpose of RLP is to - encode arbitrarily nested arrays of binary data, and RLP is the primary - encoding method used to serialize objects in Ethereum's execution layer. - The only purpose of RLP is to encode structure; encoding specific data - types (e.g. strings, floats) is left up to higher-order protocols. - - Parameters - ---------- - header : - Header of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the header. - """ - return keccak256(rlp.encode(header)) - - -def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: - """ - Validates the gas limit for a block. - - The bounds of the gas limit, ``max_adjustment_delta``, is set as the - quotient of the parent block's gas limit and the - ``GAS_LIMIT_ADJUSTMENT_FACTOR``. Therefore, if the gas limit that is - passed through as a parameter is greater than or equal to the *sum* of - the parent's gas and the adjustment delta then the limit for gas is too - high and fails this function's check. Similarly, if the limit is less - than or equal to the *difference* of the parent's gas and the adjustment - delta *or* the predefined ``GAS_LIMIT_MINIMUM`` then this function's - check fails because the gas limit doesn't allow for a sufficient or - reasonable amount of gas to be used on a block. - - Parameters - ---------- - gas_limit : - Gas limit to validate. - - parent_gas_limit : - Gas limit of the parent block. - - Returns - ------- - check : `bool` - True if gas limit constraints are satisfied, False otherwise. - """ - max_adjustment_delta = parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR - if gas_limit >= parent_gas_limit + max_adjustment_delta: - return False - if gas_limit <= parent_gas_limit - max_adjustment_delta: - return False - if gas_limit < GAS_LIMIT_MINIMUM: - return False - - return True diff --git a/src/ethereum/osaka/fork_types.py b/src/ethereum/osaka/fork_types.py deleted file mode 100644 index 26b6656190..0000000000 --- a/src/ethereum/osaka/fork_types.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Ethereum Types -^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Types re-used throughout the specification, which are specific to Ethereum. -""" - -from dataclasses import dataclass - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes20, Bytes256 -from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U8, U64, U256, Uint - -from ..crypto.hash import Hash32, keccak256 - -Address = Bytes20 -Root = Hash32 -VersionedHash = Hash32 - -Bloom = Bytes256 - - -@slotted_freezable -@dataclass -class Account: - """ - State associated with an address. - """ - - nonce: Uint - balance: U256 - code: bytes - - -EMPTY_ACCOUNT = Account( - nonce=Uint(0), - balance=U256(0), - code=bytearray(), -) - - -def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: - """ - Encode `Account` dataclass. - - Storage is not stored in the `Account` dataclass, so `Accounts` cannot be - encoded without providing a storage root. - """ - return rlp.encode( - ( - raw_account_data.nonce, - raw_account_data.balance, - storage_root, - keccak256(raw_account_data.code), - ) - ) - - -@slotted_freezable -@dataclass -class Authorization: - """ - The authorization for a set code transaction. - """ - - chain_id: U256 - address: Address - nonce: U64 - y_parity: U8 - r: U256 - s: U256 diff --git a/src/ethereum/osaka/requests.py b/src/ethereum/osaka/requests.py deleted file mode 100644 index a72f8f35ae..0000000000 --- a/src/ethereum/osaka/requests.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Requests were introduced in EIP-7685 as a a general purpose framework for -storing contract-triggered requests. It extends the execution header and -body with a single field each to store the request information. -This inherently exposes the requests to the consensus layer, which can -then process each one. - -[EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 -""" - -from hashlib import sha256 -from typing import List, Union - -from ethereum_types.bytes import Bytes - -from .blocks import Receipt, decode_receipt -from .utils.hexadecimal import hex_to_address - -DEPOSIT_CONTRACT_ADDRESS = hex_to_address( - "0x00000000219ab540356cbb839cbe05303d7705fa" -) -DEPOSIT_REQUEST_TYPE = b"\x00" -WITHDRAWAL_REQUEST_TYPE = b"\x01" -CONSOLIDATION_REQUEST_TYPE = b"\x02" - - -def extract_deposit_data(data: Bytes) -> Bytes: - """ - Extracts Deposit Request from the DepositContract.DepositEvent data. - """ - return ( - data[192:240] # public_key - + data[288:320] # withdrawal_credentials - + data[352:360] # amount - + data[416:512] # signature - + data[544:552] # index - ) - - -def parse_deposit_requests_from_receipt( - receipt: Union[Bytes, Receipt], -) -> Bytes: - """ - Parse deposit requests from a receipt. - """ - deposit_requests: Bytes = b"" - decoded_receipt = decode_receipt(receipt) - for log in decoded_receipt.logs: - if log.address == DEPOSIT_CONTRACT_ADDRESS: - request = extract_deposit_data(log.data) - deposit_requests += request - - return deposit_requests - - -def compute_requests_hash(requests: List[Bytes]) -> Bytes: - """ - Get the hash of the requests using the SHA2-256 algorithm. - - Parameters - ---------- - requests : Bytes - The requests to hash. - - Returns - ------- - requests_hash : Bytes - The hash of the requests. - """ - m = sha256() - for request in requests: - m.update(sha256(request).digest()) - - return m.digest() diff --git a/src/ethereum/osaka/state.py b/src/ethereum/osaka/state.py deleted file mode 100644 index 8a0e14728e..0000000000 --- a/src/ethereum/osaka/state.py +++ /dev/null @@ -1,720 +0,0 @@ -""" -State -^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -The state contains all information that is preserved between transactions. - -It consists of a main account trie and storage tries for each contract. - -There is a distinction between an account that does not exist and -`EMPTY_ACCOUNT`. -""" -from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional, Set, Tuple - -from ethereum_types.bytes import Bytes, Bytes32 -from ethereum_types.frozen import modify -from ethereum_types.numeric import U256, Uint - -from .fork_types import EMPTY_ACCOUNT, Account, Address, Root -from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set - - -@dataclass -class State: - """ - Contains all information that is preserved between transactions. - """ - - _main_trie: Trie[Address, Optional[Account]] = field( - default_factory=lambda: Trie(secured=True, default=None) - ) - _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( - default_factory=dict - ) - _snapshots: List[ - Tuple[ - Trie[Address, Optional[Account]], - Dict[Address, Trie[Bytes32, U256]], - ] - ] = field(default_factory=list) - created_accounts: Set[Address] = field(default_factory=set) - - -@dataclass -class TransientStorage: - """ - Contains all information that is preserved between message calls - within a transaction. - """ - - _tries: Dict[Address, Trie[Bytes32, U256]] = field(default_factory=dict) - _snapshots: List[Dict[Address, Trie[Bytes32, U256]]] = field( - default_factory=list - ) - - -def close_state(state: State) -> None: - """ - Free resources held by the state. Used by optimized implementations to - release file descriptors. - """ - del state._main_trie - del state._storage_tries - del state._snapshots - del state.created_accounts - - -def begin_transaction( - state: State, transient_storage: TransientStorage -) -> None: - """ - Start a state transaction. - - Transactions are entirely implicit and can be nested. It is not possible to - calculate the state root during a transaction. - - Parameters - ---------- - state : State - The state. - transient_storage : TransientStorage - The transient storage of the transaction. - """ - state._snapshots.append( - ( - copy_trie(state._main_trie), - {k: copy_trie(t) for (k, t) in state._storage_tries.items()}, - ) - ) - transient_storage._snapshots.append( - {k: copy_trie(t) for (k, t) in transient_storage._tries.items()} - ) - - -def commit_transaction( - state: State, transient_storage: TransientStorage -) -> None: - """ - Commit a state transaction. - - Parameters - ---------- - state : State - The state. - transient_storage : TransientStorage - The transient storage of the transaction. - """ - state._snapshots.pop() - if not state._snapshots: - state.created_accounts.clear() - - transient_storage._snapshots.pop() - - -def rollback_transaction( - state: State, transient_storage: TransientStorage -) -> None: - """ - Rollback a state transaction, resetting the state to the point when the - corresponding `start_transaction()` call was made. - - Parameters - ---------- - state : State - The state. - transient_storage : TransientStorage - The transient storage of the transaction. - """ - state._main_trie, state._storage_tries = state._snapshots.pop() - if not state._snapshots: - state.created_accounts.clear() - - transient_storage._tries = transient_storage._snapshots.pop() - - -def get_account(state: State, address: Address) -> Account: - """ - Get the `Account` object at an address. Returns `EMPTY_ACCOUNT` if there - is no account at the address. - - Use `get_account_optional()` if you care about the difference between a - non-existent account and `EMPTY_ACCOUNT`. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address to lookup. - - Returns - ------- - account : `Account` - Account at address. - """ - account = get_account_optional(state, address) - if isinstance(account, Account): - return account - else: - return EMPTY_ACCOUNT - - -def get_account_optional(state: State, address: Address) -> Optional[Account]: - """ - Get the `Account` object at an address. Returns `None` (rather than - `EMPTY_ACCOUNT`) if there is no account at the address. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address to lookup. - - Returns - ------- - account : `Account` - Account at address. - """ - account = trie_get(state._main_trie, address) - return account - - -def set_account( - state: State, address: Address, account: Optional[Account] -) -> None: - """ - Set the `Account` object at an address. Setting to `None` deletes - the account (but not its storage, see `destroy_account()`). - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address to set. - account : `Account` - Account to set at address. - """ - trie_set(state._main_trie, address, account) - - -def destroy_account(state: State, address: Address) -> None: - """ - Completely remove the account at `address` and all of its storage. - - This function is made available exclusively for the `SELFDESTRUCT` - opcode. It is expected that `SELFDESTRUCT` will be disabled in a future - hardfork and this function will be removed. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address of account to destroy. - """ - destroy_storage(state, address) - set_account(state, address, None) - - -def destroy_storage(state: State, address: Address) -> None: - """ - Completely remove the storage at `address`. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address of account whose storage is to be deleted. - """ - if address in state._storage_tries: - del state._storage_tries[address] - - -def mark_account_created(state: State, address: Address) -> None: - """ - Mark an account as having been created in the current transaction. - This information is used by `get_storage_original()` to handle an obscure - edgecase. - - The marker is not removed even if the account creation reverts. Since the - account cannot have had code prior to its creation and can't call - `get_storage_original()`, this is harmless. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address of the account that has been created. - """ - state.created_accounts.add(address) - - -def get_storage(state: State, address: Address, key: Bytes32) -> U256: - """ - Get a value at a storage key on an account. Returns `U256(0)` if the - storage key has not been set previously. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address of the account. - key : `Bytes` - Key to lookup. - - Returns - ------- - value : `U256` - Value at the key. - """ - trie = state._storage_tries.get(address) - if trie is None: - return U256(0) - - value = trie_get(trie, key) - - assert isinstance(value, U256) - return value - - -def set_storage( - state: State, address: Address, key: Bytes32, value: U256 -) -> None: - """ - Set a value at a storage key on an account. Setting to `U256(0)` deletes - the key. - - Parameters - ---------- - state: `State` - The state - address : `Address` - Address of the account. - key : `Bytes` - Key to set. - value : `U256` - Value to set at the key. - """ - assert trie_get(state._main_trie, address) is not None - - trie = state._storage_tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - state._storage_tries[address] = trie - trie_set(trie, key, value) - if trie._data == {}: - del state._storage_tries[address] - - -def storage_root(state: State, address: Address) -> Root: - """ - Calculate the storage root of an account. - - Parameters - ---------- - state: - The state - address : - Address of the account. - - Returns - ------- - root : `Root` - Storage root of the account. - """ - assert not state._snapshots - if address in state._storage_tries: - return root(state._storage_tries[address]) - else: - return EMPTY_TRIE_ROOT - - -def state_root(state: State) -> Root: - """ - Calculate the state root. - - Parameters - ---------- - state: - The current state. - - Returns - ------- - root : `Root` - The state root. - """ - assert not state._snapshots - - def get_storage_root(address: Address) -> Root: - return storage_root(state, address) - - return root(state._main_trie, get_storage_root=get_storage_root) - - -def account_exists(state: State, address: Address) -> bool: - """ - Checks if an account exists in the state trie - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - account_exists : `bool` - True if account exists in the state trie, False otherwise - """ - return get_account_optional(state, address) is not None - - -def account_has_code_or_nonce(state: State, address: Address) -> bool: - """ - Checks if an account has non zero nonce or non empty code - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : `bool` - True if the account has non zero nonce or non empty code, - False otherwise. - """ - account = get_account(state, address) - return account.nonce != Uint(0) or account.code != b"" - - -def account_has_storage(state: State, address: Address) -> bool: - """ - Checks if an account has storage. - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - has_storage : `bool` - True if the account has storage, False otherwise. - """ - return address in state._storage_tries - - -def is_account_empty(state: State, address: Address) -> bool: - """ - Checks if an account has zero nonce, empty code and zero balance. - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - is_empty : `bool` - True if if an account has zero nonce, empty code and zero balance, - False otherwise. - """ - account = get_account(state, address) - return ( - account.nonce == Uint(0) - and account.code == b"" - and account.balance == 0 - ) - - -def account_exists_and_is_empty(state: State, address: Address) -> bool: - """ - Checks if an account exists and has zero nonce, empty code and zero - balance. - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - exists_and_is_empty : `bool` - True if an account exists and has zero nonce, empty code and zero - balance, False otherwise. - """ - account = get_account_optional(state, address) - return ( - account is not None - and account.nonce == Uint(0) - and account.code == b"" - and account.balance == 0 - ) - - -def is_account_alive(state: State, address: Address) -> bool: - """ - Check whether is an account is both in the state and non empty. - - Parameters - ---------- - state: - The state - address: - Address of the account that needs to be checked. - - Returns - ------- - is_alive : `bool` - True if the account is alive. - """ - account = get_account_optional(state, address) - if account is None: - return False - else: - return not ( - account.nonce == Uint(0) - and account.code == b"" - and account.balance == 0 - ) - - -def modify_state( - state: State, address: Address, f: Callable[[Account], None] -) -> None: - """ - Modify an `Account` in the `State`. - """ - set_account(state, address, modify(get_account(state, address), f)) - - -def move_ether( - state: State, - sender_address: Address, - recipient_address: Address, - amount: U256, -) -> None: - """ - Move funds between accounts. - """ - - def reduce_sender_balance(sender: Account) -> None: - if sender.balance < amount: - raise AssertionError - sender.balance -= amount - - def increase_recipient_balance(recipient: Account) -> None: - recipient.balance += amount - - modify_state(state, sender_address, reduce_sender_balance) - modify_state(state, recipient_address, increase_recipient_balance) - - -def set_account_balance(state: State, address: Address, amount: U256) -> None: - """ - Sets the balance of an account. - - Parameters - ---------- - state: - The current state. - - address: - Address of the account whose nonce needs to be incremented. - - amount: - The amount that needs to set in balance. - """ - - def set_balance(account: Account) -> None: - account.balance = amount - - modify_state(state, address, set_balance) - - -def touch_account(state: State, address: Address) -> None: - """ - Initializes an account to state. - - Parameters - ---------- - state: - The current state. - - address: - The address of the account that need to initialised. - """ - if not account_exists(state, address): - set_account(state, address, EMPTY_ACCOUNT) - - -def increment_nonce(state: State, address: Address) -> None: - """ - Increments the nonce of an account. - - Parameters - ---------- - state: - The current state. - - address: - Address of the account whose nonce needs to be incremented. - """ - - def increase_nonce(sender: Account) -> None: - sender.nonce += Uint(1) - - modify_state(state, address, increase_nonce) - - -def set_code(state: State, address: Address, code: Bytes) -> None: - """ - Sets Account code. - - Parameters - ---------- - state: - The current state. - - address: - Address of the account whose code needs to be update. - - code: - The bytecode that needs to be set. - """ - - def write_code(sender: Account) -> None: - sender.code = code - - modify_state(state, address, write_code) - - -def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: - """ - Get the original value in a storage slot i.e. the value before the current - transaction began. This function reads the value from the snapshots taken - before executing the transaction. - - Parameters - ---------- - state: - The current state. - address: - Address of the account to read the value from. - key: - Key of the storage slot. - """ - # In the transaction where an account is created, its preexisting storage - # is ignored. - if address in state.created_accounts: - return U256(0) - - _, original_trie = state._snapshots[0] - original_account_trie = original_trie.get(address) - - if original_account_trie is None: - original_value = U256(0) - else: - original_value = trie_get(original_account_trie, key) - - assert isinstance(original_value, U256) - - return original_value - - -def get_transient_storage( - transient_storage: TransientStorage, address: Address, key: Bytes32 -) -> U256: - """ - Get a value at a storage key on an account from transient storage. - Returns `U256(0)` if the storage key has not been set previously. - Parameters - ---------- - transient_storage: `TransientStorage` - The transient storage - address : `Address` - Address of the account. - key : `Bytes` - Key to lookup. - Returns - ------- - value : `U256` - Value at the key. - """ - trie = transient_storage._tries.get(address) - if trie is None: - return U256(0) - - value = trie_get(trie, key) - - assert isinstance(value, U256) - return value - - -def set_transient_storage( - transient_storage: TransientStorage, - address: Address, - key: Bytes32, - value: U256, -) -> None: - """ - Set a value at a storage key on an account. Setting to `U256(0)` deletes - the key. - Parameters - ---------- - transient_storage: `TransientStorage` - The transient storage - address : `Address` - Address of the account. - key : `Bytes` - Key to set. - value : `U256` - Value to set at the key. - """ - trie = transient_storage._tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - transient_storage._tries[address] = trie - trie_set(trie, key, value) - if trie._data == {}: - del transient_storage._tries[address] - - -def destroy_touched_empty_accounts( - state: State, touched_accounts: Set[Address] -) -> None: - """ - Destroy all touched accounts that are empty. - Parameters - ---------- - state: `State` - The current state. - touched_accounts: `Set[Address]` - All the accounts that have been touched in the current transaction. - """ - for address in touched_accounts: - if account_exists_and_is_empty(state, address): - destroy_account(state, address) diff --git a/src/ethereum/osaka/transactions.py b/src/ethereum/osaka/transactions.py deleted file mode 100644 index 3e5adcfb46..0000000000 --- a/src/ethereum/osaka/transactions.py +++ /dev/null @@ -1,579 +0,0 @@ -""" -Transactions are atomic units of work created externally to Ethereum and -submitted to be executed. If Ethereum is viewed as a state machine, -transactions are the events that move between states. -""" -from dataclasses import dataclass -from typing import Tuple, Union - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 -from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U64, U256, Uint, ulen - -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError, InvalidTransaction - -from .exceptions import TransactionTypeError -from .fork_types import Address, Authorization, VersionedHash - -TX_BASE_COST = Uint(21000) -FLOOR_CALLDATA_COST = Uint(10) -STANDARD_CALLDATA_TOKEN_COST = Uint(4) -TX_CREATE_COST = Uint(32000) -TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) -TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) - - -@slotted_freezable -@dataclass -class LegacyTransaction: - """ - Atomic operation performed on the block chain. - """ - - nonce: U256 - gas_price: Uint - gas: Uint - to: Union[Bytes0, Address] - value: U256 - data: Bytes - v: U256 - r: U256 - s: U256 - - -@slotted_freezable -@dataclass -class AccessListTransaction: - """ - The transaction type added in EIP-2930 to support access lists. - """ - - chain_id: U64 - nonce: U256 - gas_price: Uint - gas: Uint - to: Union[Bytes0, Address] - value: U256 - data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] - y_parity: U256 - r: U256 - s: U256 - - -@slotted_freezable -@dataclass -class FeeMarketTransaction: - """ - The transaction type added in EIP-1559. - """ - - chain_id: U64 - nonce: U256 - max_priority_fee_per_gas: Uint - max_fee_per_gas: Uint - gas: Uint - to: Union[Bytes0, Address] - value: U256 - data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] - y_parity: U256 - r: U256 - s: U256 - - -@slotted_freezable -@dataclass -class BlobTransaction: - """ - The transaction type added in EIP-4844. - """ - - chain_id: U64 - nonce: U256 - max_priority_fee_per_gas: Uint - max_fee_per_gas: Uint - gas: Uint - to: Address - value: U256 - data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] - max_fee_per_blob_gas: U256 - blob_versioned_hashes: Tuple[VersionedHash, ...] - y_parity: U256 - r: U256 - s: U256 - - -@slotted_freezable -@dataclass -class SetCodeTransaction: - """ - The transaction type added in EIP-7702. - """ - - chain_id: U64 - nonce: U64 - max_priority_fee_per_gas: Uint - max_fee_per_gas: Uint - gas: Uint - to: Address - value: U256 - data: Bytes - access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...] - authorizations: Tuple[Authorization, ...] - y_parity: U256 - r: U256 - s: U256 - - -Transaction = Union[ - LegacyTransaction, - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, -] - - -def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]: - """ - Encode a transaction. Needed because non-legacy transactions aren't RLP. - """ - if isinstance(tx, LegacyTransaction): - return tx - elif isinstance(tx, AccessListTransaction): - return b"\x01" + rlp.encode(tx) - elif isinstance(tx, FeeMarketTransaction): - return b"\x02" + rlp.encode(tx) - elif isinstance(tx, BlobTransaction): - return b"\x03" + rlp.encode(tx) - elif isinstance(tx, SetCodeTransaction): - return b"\x04" + rlp.encode(tx) - else: - raise Exception(f"Unable to encode transaction of type {type(tx)}") - - -def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: - """ - Decode a transaction. Needed because non-legacy transactions aren't RLP. - """ - if isinstance(tx, Bytes): - if tx[0] == 1: - return rlp.decode_to(AccessListTransaction, tx[1:]) - elif tx[0] == 2: - return rlp.decode_to(FeeMarketTransaction, tx[1:]) - elif tx[0] == 3: - return rlp.decode_to(BlobTransaction, tx[1:]) - elif tx[0] == 4: - return rlp.decode_to(SetCodeTransaction, tx[1:]) - else: - raise TransactionTypeError(tx[0]) - else: - return tx - - -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - intrinsic_gas : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - calldata_floor_gas_cost : `ethereum.base_types.Uint` - The eip-7623 minimum gas cost charged to the transaction - based on the calldata size. - - Raises - ------ - InvalidBlock : - If the transaction is not valid. - """ - from .vm.interpreter import MAX_CODE_SIZE - - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: - raise InvalidTransaction("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise InvalidTransaction("Nonce too high") - if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - raise InvalidTransaction("Code size too large") - - return intrinsic_gas, calldata_floor_gas_cost - - -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - intrinsic_gas : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - calldata_floor_gas_cost : `ethereum.base_types.Uint` - The eip-7623 minimum gas cost used by the transaction - based on the calldata size. - """ - from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST - from .vm.gas import init_code_cost - - zero_bytes = 0 - for byte in tx.data: - if byte == 0: - zero_bytes += 1 - - tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4) - # EIP-7623 floor price (note: no EVM costs) - calldata_floor_gas_cost = ( - tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST - ) - - data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) - else: - create_cost = Uint(0) - - access_list_cost = Uint(0) - if isinstance( - tx, - ( - AccessListTransaction, - FeeMarketTransaction, - BlobTransaction, - SetCodeTransaction, - ), - ): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - auth_cost = Uint(0) - if isinstance(tx, SetCodeTransaction): - auth_cost += Uint(PER_EMPTY_ACCOUNT_COST * len(tx.authorizations)) - - return ( - Uint( - TX_BASE_COST - + data_cost - + create_cost - + access_list_cost - + auth_cost - ), - calldata_floor_gas_cost, - ) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidSignatureError("bad r") - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidSignatureError("bad s") - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - if tx.y_parity not in (U256(0), U256(1)): - raise InvalidSignatureError("bad y_parity") - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - if tx.y_parity not in (U256(0), U256(1)): - raise InvalidSignatureError("bad y_parity") - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - elif isinstance(tx, BlobTransaction): - if tx.y_parity not in (U256(0), U256(1)): - raise InvalidSignatureError("bad y_parity") - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_4844(tx) - ) - elif isinstance(tx, SetCodeTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_7702(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_4844(tx: BlobTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP-4844 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x03" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - tx.max_fee_per_blob_gas, - tx.blob_versioned_hashes, - ) - ) - ) - - -def signing_hash_7702(tx: SetCodeTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP-7702 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x04" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - tx.authorizations, - ) - ) - ) - - -def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: - """ - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - assert isinstance(tx, (LegacyTransaction, Bytes)) - if isinstance(tx, LegacyTransaction): - return keccak256(rlp.encode(tx)) - else: - return keccak256(tx) diff --git a/src/ethereum/osaka/trie.py b/src/ethereum/osaka/trie.py deleted file mode 100644 index c81ef86b61..0000000000 --- a/src/ethereum/osaka/trie.py +++ /dev/null @@ -1,494 +0,0 @@ -""" -State Trie -^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -The state trie is the structure responsible for storing -`.fork_types.Account` objects. -""" - -import copy -from dataclasses import dataclass, field -from typing import ( - Callable, - Dict, - Generic, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, -) - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes -from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint -from typing_extensions import assert_type - -from ethereum.cancun import trie as previous_trie -from ethereum.crypto.hash import keccak256 -from ethereum.utils.hexadecimal import hex_to_bytes - -from .blocks import Receipt, Withdrawal -from .fork_types import Account, Address, Root, encode_account -from .transactions import LegacyTransaction - -# note: an empty trie (regardless of whether it is secured) has root: -# -# keccak256(RLP(b'')) -# == -# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 # noqa: E501,SC10 -# -# also: -# -# keccak256(RLP(())) -# == -# 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 # noqa: E501,SC10 -# -# which is the sha3Uncles hash in block header with no uncles -EMPTY_TRIE_ROOT = Root( - hex_to_bytes( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ) -) - -Node = Union[ - Account, Bytes, LegacyTransaction, Receipt, Uint, U256, Withdrawal, None -] -K = TypeVar("K", bound=Bytes) -V = TypeVar( - "V", - Optional[Account], - Optional[Bytes], - Bytes, - Optional[Union[LegacyTransaction, Bytes]], - Optional[Union[Receipt, Bytes]], - Optional[Union[Withdrawal, Bytes]], - Uint, - U256, -) - - -@slotted_freezable -@dataclass -class LeafNode: - """Leaf node in the Merkle Trie""" - - rest_of_key: Bytes - value: rlp.Extended - - -@slotted_freezable -@dataclass -class ExtensionNode: - """Extension node in the Merkle Trie""" - - key_segment: Bytes - subnode: rlp.Extended - - -BranchSubnodes = Tuple[ - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, - rlp.Extended, -] - - -@slotted_freezable -@dataclass -class BranchNode: - """Branch node in the Merkle Trie""" - - subnodes: BranchSubnodes - value: rlp.Extended - - -InternalNode = Union[LeafNode, ExtensionNode, BranchNode] - - -def encode_internal_node(node: Optional[InternalNode]) -> rlp.Extended: - """ - Encodes a Merkle Trie node into its RLP form. The RLP will then be - serialized into a `Bytes` and hashed unless it is less that 32 bytes - when serialized. - - This function also accepts `None`, representing the absence of a node, - which is encoded to `b""`. - - Parameters - ---------- - node : Optional[InternalNode] - The node to encode. - - Returns - ------- - encoded : `rlp.Extended` - The node encoded as RLP. - """ - unencoded: rlp.Extended - if node is None: - unencoded = b"" - elif isinstance(node, LeafNode): - unencoded = ( - nibble_list_to_compact(node.rest_of_key, True), - node.value, - ) - elif isinstance(node, ExtensionNode): - unencoded = ( - nibble_list_to_compact(node.key_segment, False), - node.subnode, - ) - elif isinstance(node, BranchNode): - unencoded = list(node.subnodes) + [node.value] - else: - raise AssertionError(f"Invalid internal node type {type(node)}!") - - encoded = rlp.encode(unencoded) - if len(encoded) < 32: - return unencoded - else: - return keccak256(encoded) - - -def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: - """ - Encode a Node for storage in the Merkle Trie. - - Currently mostly an unimplemented stub. - """ - if isinstance(node, Account): - assert storage_root is not None - return encode_account(node, storage_root) - elif isinstance(node, (LegacyTransaction, Receipt, Withdrawal, U256)): - return rlp.encode(node) - elif isinstance(node, Bytes): - return node - else: - return previous_trie.encode_node(node, storage_root) - - -@dataclass -class Trie(Generic[K, V]): - """ - The Merkle Trie. - """ - - secured: bool - default: V - _data: Dict[K, V] = field(default_factory=dict) - - -def copy_trie(trie: Trie[K, V]) -> Trie[K, V]: - """ - Create a copy of `trie`. Since only frozen objects may be stored in tries, - the contents are reused. - - Parameters - ---------- - trie: `Trie` - Trie to copy. - - Returns - ------- - new_trie : `Trie[K, V]` - A copy of the trie. - """ - return Trie(trie.secured, trie.default, copy.copy(trie._data)) - - -def trie_set(trie: Trie[K, V], key: K, value: V) -> None: - """ - Stores an item in a Merkle Trie. - - This method deletes the key if `value == trie.default`, because the Merkle - Trie represents the default value by omitting it from the trie. - - Parameters - ---------- - trie: `Trie` - Trie to store in. - key : `Bytes` - Key to lookup. - value : `V` - Node to insert at `key`. - """ - if value == trie.default: - if key in trie._data: - del trie._data[key] - else: - trie._data[key] = value - - -def trie_get(trie: Trie[K, V], key: K) -> V: - """ - Gets an item from the Merkle Trie. - - This method returns `trie.default` if the key is missing. - - Parameters - ---------- - trie: - Trie to lookup in. - key : - Key to lookup. - - Returns - ------- - node : `V` - Node at `key` in the trie. - """ - return trie._data.get(key, trie.default) - - -def common_prefix_length(a: Sequence, b: Sequence) -> int: - """ - Find the longest common prefix of two sequences. - """ - for i in range(len(a)): - if i >= len(b) or a[i] != b[i]: - return i - return len(a) - - -def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes: - """ - Compresses nibble-list into a standard byte array with a flag. - - A nibble-list is a list of byte values no greater than `15`. The flag is - encoded in high nibble of the highest byte. The flag nibble can be broken - down into two two-bit flags. - - Highest nibble:: - - +---+---+----------+--------+ - | _ | _ | is_leaf | parity | - +---+---+----------+--------+ - 3 2 1 0 - - - The lowest bit of the nibble encodes the parity of the length of the - remaining nibbles -- `0` when even and `1` when odd. The second lowest bit - is used to distinguish leaf and extension nodes. The other two bits are not - used. - - Parameters - ---------- - x : - Array of nibbles. - is_leaf : - True if this is part of a leaf node, or false if it is an extension - node. - - Returns - ------- - compressed : `bytearray` - Compact byte array. - """ - compact = bytearray() - - if len(x) % 2 == 0: # ie even length - compact.append(16 * (2 * is_leaf)) - for i in range(0, len(x), 2): - compact.append(16 * x[i] + x[i + 1]) - else: - compact.append(16 * ((2 * is_leaf) + 1) + x[0]) - for i in range(1, len(x), 2): - compact.append(16 * x[i] + x[i + 1]) - - return Bytes(compact) - - -def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: - """ - Converts a `Bytes` into to a sequence of nibbles (bytes with value < 16). - - Parameters - ---------- - bytes_: - The `Bytes` to convert. - - Returns - ------- - nibble_list : `Bytes` - The `Bytes` in nibble-list format. - """ - nibble_list = bytearray(2 * len(bytes_)) - for byte_index, byte in enumerate(bytes_): - nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 - nibble_list[byte_index * 2 + 1] = byte & 0x0F - return Bytes(nibble_list) - - -def _prepare_trie( - trie: Trie[K, V], - get_storage_root: Optional[Callable[[Address], Root]] = None, -) -> Mapping[Bytes, Bytes]: - """ - Prepares the trie for root calculation. Removes values that are empty, - hashes the keys (if `secured == True`) and encodes all the nodes. - - Parameters - ---------- - trie : - The `Trie` to prepare. - get_storage_root : - Function to get the storage root of an account. Needed to encode - `Account` objects. - - Returns - ------- - out : `Mapping[ethereum.base_types.Bytes, Node]` - Object with keys mapped to nibble-byte form. - """ - mapped: MutableMapping[Bytes, Bytes] = {} - - for preimage, value in trie._data.items(): - if isinstance(value, Account): - assert get_storage_root is not None - address = Address(preimage) - encoded_value = encode_node(value, get_storage_root(address)) - else: - encoded_value = encode_node(value) - if encoded_value == b"": - raise AssertionError - key: Bytes - if trie.secured: - # "secure" tries hash keys once before construction - key = keccak256(preimage) - else: - key = preimage - mapped[bytes_to_nibble_list(key)] = encoded_value - - return mapped - - -def root( - trie: Trie[K, V], - get_storage_root: Optional[Callable[[Address], Root]] = None, -) -> Root: - """ - Computes the root of a modified merkle patricia trie (MPT). - - Parameters - ---------- - trie : - `Trie` to get the root of. - get_storage_root : - Function to get the storage root of an account. Needed to encode - `Account` objects. - - - Returns - ------- - root : `.fork_types.Root` - MPT root of the underlying key-value pairs. - """ - obj = _prepare_trie(trie, get_storage_root) - - root_node = encode_internal_node(patricialize(obj, Uint(0))) - if len(rlp.encode(root_node)) < 32: - return keccak256(rlp.encode(root_node)) - else: - assert isinstance(root_node, Bytes) - return Root(root_node) - - -def patricialize( - obj: Mapping[Bytes, Bytes], level: Uint -) -> Optional[InternalNode]: - """ - Structural composition function. - - Used to recursively patricialize and merkleize a dictionary. Includes - memoization of the tree structure and hashes. - - Parameters - ---------- - obj : - Underlying trie key-value pairs, with keys in nibble-list format. - level : - Current trie level. - - Returns - ------- - node : `ethereum.base_types.Bytes` - Root node of `obj`. - """ - if len(obj) == 0: - return None - - arbitrary_key = next(iter(obj)) - - # if leaf node - if len(obj) == 1: - leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) - return leaf - - # prepare for extension node check by finding max j such that all keys in - # obj have the same key[i:j] - substring = arbitrary_key[level:] - prefix_length = len(substring) - for key in obj: - prefix_length = min( - prefix_length, common_prefix_length(substring, key[level:]) - ) - - # finished searching, found another key at the current level - if prefix_length == 0: - break - - # if extension node - if prefix_length > 0: - prefix = arbitrary_key[int(level) : int(level) + prefix_length] - return ExtensionNode( - prefix, - encode_internal_node( - patricialize(obj, level + Uint(prefix_length)) - ), - ) - - branches: List[MutableMapping[Bytes, Bytes]] = [] - for _ in range(16): - branches.append({}) - value = b"" - for key in obj: - if len(key) == level: - # shouldn't ever have an account or receipt in an internal node - if isinstance(obj[key], (Account, Receipt, Uint)): - raise AssertionError - value = obj[key] - else: - branches[key[level]][key] = obj[key] - - subnodes = tuple( - encode_internal_node(patricialize(branches[k], level + Uint(1))) - for k in range(16) - ) - return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[rlp.Extended, ...])), - value, - ) diff --git a/src/ethereum/osaka/utils/__init__.py b/src/ethereum/osaka/utils/__init__.py deleted file mode 100644 index 224a4d269b..0000000000 --- a/src/ethereum/osaka/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Utility functions unique to this particular fork. -""" diff --git a/src/ethereum/osaka/utils/address.py b/src/ethereum/osaka/utils/address.py deleted file mode 100644 index 1872dcf317..0000000000 --- a/src/ethereum/osaka/utils/address.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Hardfork Utility Functions For Addresses -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Address specific functions used in this osaka version of -specification. -""" -from typing import Union - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes32 -from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.hash import keccak256 -from ethereum.utils.byte import left_pad_zero_bytes - -from ..fork_types import Address - - -def to_address(data: Union[Uint, U256]) -> Address: - """ - Convert a Uint or U256 value to a valid address (20 bytes). - - Parameters - ---------- - data : - The string to be converted to bytes. - - Returns - ------- - address : `Address` - The obtained address. - """ - return Address(data.to_be_bytes32()[-20:]) - - -def compute_contract_address(address: Address, nonce: Uint) -> Address: - """ - Computes address of the new account that needs to be created. - - Parameters - ---------- - address : - The address of the account that wants to create the new account. - nonce : - The transaction count of the account that wants to create the new - account. - - Returns - ------- - address: `Address` - The computed address of the new account. - """ - computed_address = keccak256(rlp.encode([address, nonce])) - canonical_address = computed_address[-20:] - padded_address = left_pad_zero_bytes(canonical_address, 20) - return Address(padded_address) - - -def compute_create2_contract_address( - address: Address, salt: Bytes32, call_data: bytearray -) -> Address: - """ - Computes address of the new account that needs to be created, which is - based on the sender address, salt and the call data as well. - - Parameters - ---------- - address : - The address of the account that wants to create the new account. - salt : - Address generation salt. - call_data : - The code of the new account which is to be created. - - Returns - ------- - address: `ethereum.osaka.fork_types.Address` - The computed address of the new account. - """ - preimage = b"\xff" + address + salt + keccak256(call_data) - computed_address = keccak256(preimage) - canonical_address = computed_address[-20:] - padded_address = left_pad_zero_bytes(canonical_address, 20) - - return Address(padded_address) diff --git a/src/ethereum/osaka/utils/hexadecimal.py b/src/ethereum/osaka/utils/hexadecimal.py deleted file mode 100644 index 738ee85d4c..0000000000 --- a/src/ethereum/osaka/utils/hexadecimal.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Utility Functions For Hexadecimal Strings -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Hexadecimal utility functions used in this specification, specific to -Osaka types. -""" -from ethereum.utils.hexadecimal import remove_hex_prefix - -from ..fork_types import Address, Bloom, Root - - -def hex_to_root(hex_string: str) -> Root: - """ - Convert hex string to trie root. - - Parameters - ---------- - hex_string : - The hexadecimal string to be converted to trie root. - - Returns - ------- - root : `Root` - Trie root obtained from the given hexadecimal string. - """ - return Root(bytes.fromhex(remove_hex_prefix(hex_string))) - - -def hex_to_bloom(hex_string: str) -> Bloom: - """ - Convert hex string to bloom. - - Parameters - ---------- - hex_string : - The hexadecimal string to be converted to bloom. - - Returns - ------- - bloom : `Bloom` - Bloom obtained from the given hexadecimal string. - """ - return Bloom(bytes.fromhex(remove_hex_prefix(hex_string))) - - -def hex_to_address(hex_string: str) -> Address: - """ - Convert hex string to Address (20 bytes). - - Parameters - ---------- - hex_string : - The hexadecimal string to be converted to Address. - - Returns - ------- - address : `Address` - The address obtained from the given hexadecimal string. - """ - return Address(bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py deleted file mode 100644 index eac49d7a2f..0000000000 --- a/src/ethereum/osaka/utils/message.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Hardfork Utility Functions For The Message Data-structure -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Message specific functions used in this osaka version of -specification. -""" -from ethereum_types.bytes import Bytes, Bytes0 -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.eoa_delegation import get_delegated_code_address -from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from .address import compute_contract_address - - -def prepare_message( - block_env: BlockEnvironment, - tx_env: TransactionEnvironment, - tx: Transaction, -) -> Message: - """ - Execute a transaction against the provided environment. - - Parameters - ---------- - block_env : - Environment for the Ethereum Virtual Machine. - tx_env : - Environment for the transaction. - tx : - Transaction to be executed. - - Returns - ------- - message: `ethereum.osaka.vm.Message` - Items containing contract creation or message call specific data. - """ - accessed_addresses = set() - accessed_addresses.add(tx_env.origin) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(tx_env.access_list_addresses) - - if isinstance(tx.to, Bytes0): - current_target = compute_contract_address( - tx_env.origin, - get_account(block_env.state, tx_env.origin).nonce - Uint(1), - ) - msg_data = Bytes(b"") - code = tx.data - code_address = None - elif isinstance(tx.to, Address): - current_target = tx.to - msg_data = tx.data - code = get_account(block_env.state, tx.to).code - - delegated_address = get_delegated_code_address(code) - if delegated_address is not None: - accessed_addresses.add(delegated_address) - code = get_account(block_env.state, delegated_address).code - - code_address = tx.to - else: - raise AssertionError("Target must be address or empty bytes") - - accessed_addresses.add(current_target) - - return Message( - block_env=block_env, - tx_env=tx_env, - caller=tx_env.origin, - target=tx.to, - gas=tx_env.gas, - value=tx.value, - data=msg_data, - code=code, - depth=Uint(0), - current_target=current_target, - code_address=code_address, - should_transfer_value=True, - is_static=False, - accessed_addresses=accessed_addresses, - accessed_storage_keys=set(tx_env.access_list_storage_keys), - parent_evm=None, - ) diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py deleted file mode 100644 index 55f1b92499..0000000000 --- a/src/ethereum/osaka/vm/__init__.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -The abstract computer which runs the code stored in an -`.fork_types.Account`. -""" - -from dataclasses import dataclass, field -from typing import List, Optional, Set, Tuple, Union - -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 ..blocks import Log, Receipt, Withdrawal -from ..fork_types import Address, Authorization, VersionedHash -from ..state import State, TransientStorage, account_exists_and_is_empty -from ..transactions import LegacyTransaction -from ..trie import Trie -from .precompiled_contracts import RIPEMD160_ADDRESS - -__all__ = ("Environment", "Evm", "Message") - - -@dataclass -class BlockEnvironment: - """ - Items external to the virtual machine itself, provided by the environment. - """ - - chain_id: U64 - state: State - block_gas_limit: Uint - block_hashes: List[Hash32] - coinbase: Address - number: Uint - base_fee_per_gas: Uint - time: U256 - prev_randao: Bytes32 - excess_blob_gas: U64 - parent_beacon_block_root: Hash32 - - -@dataclass -class BlockOutput: - """ - Output from applying the block body to the present state. - - Contains the following: - - block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. - transactions_trie : `ethereum.fork_types.Root` - Trie of all the transactions in the block. - receipts_trie : `ethereum.fork_types.Root` - Trie root of all the receipts in the block. - block_logs : `Bloom` - Logs bloom of all the logs included in all the transactions of the - block. - withdrawals_trie : `ethereum.fork_types.Root` - Trie root of all the withdrawals in the block. - blob_gas_used : `ethereum.base_types.Uint` - Total blob gas used in the block. - requests : `Bytes` - Hash of all the requests in the block. - """ - - block_gas_used: Uint = Uint(0) - transactions_trie: Trie[ - Bytes, Optional[Union[Bytes, LegacyTransaction]] - ] = field(default_factory=lambda: Trie(secured=False, default=None)) - receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( - default_factory=lambda: Trie(secured=False, default=None) - ) - block_logs: Tuple[Log, ...] = field(default_factory=tuple) - withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( - default_factory=lambda: Trie(secured=False, default=None) - ) - blob_gas_used: Uint = Uint(0) - deposit_requests: Bytes = Bytes(b"") - requests: List[Bytes] = field(default_factory=list) - - -@dataclass -class TransactionEnvironment: - """ - Items that are used by contract creation or message call. - """ - - origin: Address - gas_price: Uint - gas: Uint - access_list_addresses: Set[Address] - access_list_storage_keys: Set[Tuple[Address, Bytes32]] - transient_storage: TransientStorage - blob_versioned_hashes: Tuple[VersionedHash, ...] - authorizations: Tuple[Authorization, ...] - index_in_block: Optional[Uint] - tx_hash: Optional[Hash32] - traces: List[dict] - - -@dataclass -class Message: - """ - Items that are used by contract creation or message call. - """ - - block_env: BlockEnvironment - tx_env: TransactionEnvironment - caller: Address - target: Union[Bytes0, Address] - current_target: Address - gas: Uint - value: U256 - data: Bytes - code_address: Optional[Address] - code: Bytes - depth: Uint - should_transfer_value: bool - is_static: bool - accessed_addresses: Set[Address] - accessed_storage_keys: Set[Tuple[Address, Bytes32]] - parent_evm: Optional["Evm"] - - -@dataclass -class Evm: - """The internal state of the virtual machine.""" - - pc: Uint - stack: List[U256] - memory: bytearray - code: Bytes - gas_left: Uint - valid_jump_destinations: Set[Uint] - logs: Tuple[Log, ...] - refund_counter: int - running: bool - message: Message - output: Bytes - accounts_to_delete: Set[Address] - touched_accounts: Set[Address] - return_data: Bytes - error: Optional[EthereumException] - accessed_addresses: Set[Address] - accessed_storage_keys: Set[Tuple[Address, Bytes32]] - - -def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: - """ - Incorporate the state of a successful `child_evm` into the parent `evm`. - - Parameters - ---------- - evm : - The parent `EVM`. - child_evm : - The child evm to incorporate. - """ - evm.gas_left += child_evm.gas_left - evm.logs += child_evm.logs - evm.refund_counter += child_evm.refund_counter - evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.touched_accounts.update(child_evm.touched_accounts) - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(child_evm.message.current_target) - evm.accessed_addresses.update(child_evm.accessed_addresses) - evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) - - -def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: - """ - Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. - - Parameters - ---------- - evm : - The parent `EVM`. - child_evm : - The child evm to incorporate. - """ - # In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was - # cleared despite running out of gas. This is an obscure edge case that can - # only happen to a precompile. - # According to the general rules governing clearing of empty accounts, the - # touch should have been reverted. Due to client bugs, this event went - # unnoticed and 0x3 has been exempted from the rule that touches are - # reverted in order to preserve this historical behaviour. - if RIPEMD160_ADDRESS in child_evm.touched_accounts: - evm.touched_accounts.add(RIPEMD160_ADDRESS) - if child_evm.message.current_target == RIPEMD160_ADDRESS: - if account_exists_and_is_empty( - evm.message.block_env.state, child_evm.message.current_target - ): - evm.touched_accounts.add(RIPEMD160_ADDRESS) - evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py deleted file mode 100644 index bb6a7e33d9..0000000000 --- a/src/ethereum/osaka/vm/eoa_delegation.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -Set EOA account code. -""" - - -from typing import Optional, Tuple - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover -from ethereum.crypto.hash import keccak256 -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 - -SET_CODE_TX_MAGIC = b"\x05" -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 -PER_AUTH_BASE_COST = 12500 -NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") - - -def is_valid_delegation(code: bytes) -> bool: - """ - Whether the code is a valid delegation designation. - - Parameters - ---------- - code: `bytes` - The code to check. - - Returns - ------- - valid : `bool` - True if the code is a valid delegation designation, - False otherwise. - """ - if ( - len(code) == EOA_DELEGATED_CODE_LENGTH - and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False - - -def get_delegated_code_address(code: bytes) -> Optional[Address]: - """ - Get the address to which the code delegates. - - Parameters - ---------- - code: `bytes` - The code to get the address from. - - Returns - ------- - address : `Optional[Address]` - The address of the delegated code. - """ - if is_valid_delegation(code): - return Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - return None - - -def recover_authority(authorization: Authorization) -> Address: - """ - Recover the authority address from the authorization. - - Parameters - ---------- - authorization - The authorization to recover the authority from. - - Raises - ------ - InvalidSignatureError - If the signature is invalid. - - Returns - ------- - authority : `Address` - The recovered authority address. - """ - y_parity, r, s = authorization.y_parity, authorization.r, authorization.s - if y_parity not in (0, 1): - raise InvalidSignatureError("Invalid y_parity in authorization") - if U256(0) >= r or r >= SECP256K1N: - raise InvalidSignatureError("Invalid r value in authorization") - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidSignatureError("Invalid s value in authorization") - - signing_hash = keccak256( - SET_CODE_TX_MAGIC - + rlp.encode( - ( - authorization.chain_id, - authorization.address, - authorization.nonce, - ) - ) - ) - - public_key = secp256k1_recover(r, s, U256(y_parity), signing_hash) - return Address(keccak256(public_key)[12:32]) - - -def access_delegation( - evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: - """ - Get the delegation address, code, and the cost of access from the address. - - Parameters - ---------- - evm : `Evm` - The execution frame. - address : `Address` - The address to get the delegation from. - - Returns - ------- - 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 - if not is_valid_delegation(code): - return False, address, code, Uint(0) - - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - code = get_account(state, address).code - - return True, address, code, access_gas_cost - - -def set_delegation(message: Message) -> U256: - """ - Set the delegation code for the authorities in the message. - - Parameters - ---------- - message : - Transaction specific items. - env : - External items required for EVM execution. - - Returns - ------- - refund_counter: `U256` - Refund from authority which already exists in state. - """ - state = message.block_env.state - refund_counter = U256(0) - for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(state, authority) - authority_code = authority_account.code - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue - - if account_exists(state, 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) - - increment_nonce(state, authority) - - if message.code_address is None: - raise InvalidBlock("Invalid type 4 transaction: no target") - message.code = get_account(state, message.code_address).code - - if is_valid_delegation(message.code): - message.code_address = Address( - message.code[EOA_DELEGATION_MARKER_LENGTH:] - ) - message.accessed_addresses.add(message.code_address) - - message.code = get_account(state, message.code_address).code - - return refund_counter diff --git a/src/ethereum/osaka/vm/exceptions.py b/src/ethereum/osaka/vm/exceptions.py deleted file mode 100644 index 2a4f2d2f65..0000000000 --- a/src/ethereum/osaka/vm/exceptions.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Exceptions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Exceptions which cause the EVM to halt exceptionally. -""" - -from ethereum.exceptions import EthereumException - - -class ExceptionalHalt(EthereumException): - """ - Indicates that the EVM has experienced an exceptional halt. This causes - execution to immediately end with all gas being consumed. - """ - - -class Revert(EthereumException): - """ - Raised by the `REVERT` opcode. - - Unlike other EVM exceptions this does not result in the consumption of all - gas. - """ - - pass - - -class StackUnderflowError(ExceptionalHalt): - """ - Occurs when a pop is executed on an empty stack. - """ - - pass - - -class StackOverflowError(ExceptionalHalt): - """ - Occurs when a push is executed on a stack at max capacity. - """ - - pass - - -class OutOfGasError(ExceptionalHalt): - """ - Occurs when an operation costs more than the amount of gas left in the - frame. - """ - - pass - - -class InvalidOpcode(ExceptionalHalt): - """ - Raised when an invalid opcode is encountered. - """ - - code: int - - def __init__(self, code: int) -> None: - super().__init__(code) - self.code = code - - -class InvalidJumpDestError(ExceptionalHalt): - """ - Occurs when the destination of a jump operation doesn't meet any of the - following criteria: - - * The jump destination is less than the length of the code. - * The jump destination should have the `JUMPDEST` opcode (0x5B). - * The jump destination shouldn't be part of the data corresponding to - `PUSH-N` opcodes. - """ - - -class StackDepthLimitError(ExceptionalHalt): - """ - Raised when the message depth is greater than `1024` - """ - - pass - - -class WriteInStaticContext(ExceptionalHalt): - """ - Raised when an attempt is made to modify the state while operating inside - of a STATICCALL context. - """ - - pass - - -class OutOfBoundsRead(ExceptionalHalt): - """ - Raised when an attempt was made to read data beyond the - boundaries of the buffer. - """ - - pass - - -class InvalidParameter(ExceptionalHalt): - """ - Raised when invalid parameters are passed. - """ - - pass - - -class InvalidContractPrefix(ExceptionalHalt): - """ - Raised when the new contract code starts with 0xEF. - """ - - pass - - -class AddressCollision(ExceptionalHalt): - """ - Raised when the new contract address has a collision. - """ - - pass - - -class KZGProofError(ExceptionalHalt): - """ - Raised when the point evaluation precompile can't verify a proof. - """ - - pass diff --git a/src/ethereum/osaka/vm/gas.py b/src/ethereum/osaka/vm/gas.py deleted file mode 100644 index 624a8f86e2..0000000000 --- a/src/ethereum/osaka/vm/gas.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Gas -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -EVM gas constants and calculators. -""" -from dataclasses import dataclass -from typing import List, Tuple - -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.trace import GasAndRefund, evm_trace -from ethereum.utils.numeric import ceil32, taylor_exponential - -from ..blocks import Header -from ..transactions import BlobTransaction, Transaction -from . import Evm -from .exceptions import OutOfGasError - -GAS_JUMPDEST = Uint(1) -GAS_BASE = Uint(2) -GAS_VERY_LOW = Uint(3) -GAS_STORAGE_SET = Uint(20000) -GAS_STORAGE_UPDATE = Uint(5000) -GAS_STORAGE_CLEAR_REFUND = Uint(4800) -GAS_LOW = Uint(5) -GAS_MID = Uint(8) -GAS_HIGH = Uint(10) -GAS_EXPONENTIATION = Uint(10) -GAS_EXPONENTIATION_PER_BYTE = Uint(50) -GAS_MEMORY = Uint(3) -GAS_KECCAK256 = Uint(30) -GAS_KECCAK256_WORD = Uint(6) -GAS_COPY = Uint(3) -GAS_BLOCK_HASH = Uint(20) -GAS_LOG = Uint(375) -GAS_LOG_DATA = Uint(8) -GAS_LOG_TOPIC = Uint(375) -GAS_CREATE = Uint(32000) -GAS_CODE_DEPOSIT = Uint(200) -GAS_ZERO = Uint(0) -GAS_NEW_ACCOUNT = Uint(25000) -GAS_CALL_VALUE = Uint(9000) -GAS_CALL_STIPEND = Uint(2300) -GAS_SELF_DESTRUCT = Uint(5000) -GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000) -GAS_ECRECOVER = Uint(3000) -GAS_SHA256 = Uint(60) -GAS_SHA256_WORD = Uint(12) -GAS_RIPEMD160 = Uint(600) -GAS_RIPEMD160_WORD = Uint(120) -GAS_IDENTITY = Uint(15) -GAS_IDENTITY_WORD = Uint(3) -GAS_RETURN_DATA_COPY = Uint(3) -GAS_FAST_STEP = Uint(5) -GAS_BLAKE2_PER_ROUND = Uint(1) -GAS_COLD_SLOAD = Uint(2100) -GAS_COLD_ACCOUNT_ACCESS = Uint(2600) -GAS_WARM_ACCESS = Uint(100) -GAS_INIT_CODE_WORD_COST = Uint(2) -GAS_BLOBHASH_OPCODE = Uint(3) -GAS_POINT_EVALUATION = Uint(50000) - -TARGET_BLOB_GAS_PER_BLOCK = U64(786432) -GAS_PER_BLOB = Uint(2**17) -MIN_BLOB_GASPRICE = Uint(1) -BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716) - -GAS_BLS_G1_ADD = Uint(375) -GAS_BLS_G1_MUL = Uint(12000) -GAS_BLS_G1_MAP = Uint(5500) -GAS_BLS_G2_ADD = Uint(600) -GAS_BLS_G2_MUL = Uint(22500) -GAS_BLS_G2_MAP = Uint(23800) - - -@dataclass -class ExtendMemory: - """ - Define the parameters for memory extension in opcodes - - `cost`: `ethereum.base_types.Uint` - The gas required to perform the extension - `expand_by`: `ethereum.base_types.Uint` - The size by which the memory will be extended - """ - - cost: Uint - expand_by: Uint - - -@dataclass -class MessageCallGas: - """ - Define the gas cost and stipend for executing the call opcodes. - - `cost`: `ethereum.base_types.Uint` - The non-refundable portion of gas reserved for executing the - call opcode. - `stipend`: `ethereum.base_types.Uint` - The portion of gas available to sub-calls that is refundable - if not consumed - """ - - cost: Uint - stipend: Uint - - -def charge_gas(evm: Evm, amount: Uint) -> None: - """ - Subtracts `amount` from `evm.gas_left`. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas the current operation requires. - - """ - evm_trace(evm, GasAndRefund(int(amount))) - - if evm.gas_left < amount: - raise OutOfGasError - else: - evm.gas_left -= amount - - -def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: - """ - Calculates the gas cost for allocating memory - to the smallest multiple of 32 bytes, - such that the allocated size is at least as big as the given size. - - Parameters - ---------- - size_in_bytes : - The size of the data in bytes. - - Returns - ------- - total_gas_cost : `ethereum.base_types.Uint` - The gas cost for storing data in memory. - """ - size_in_words = ceil32(size_in_bytes) // Uint(32) - linear_cost = size_in_words * GAS_MEMORY - quadratic_cost = size_in_words ** Uint(2) // Uint(512) - total_gas_cost = linear_cost + quadratic_cost - try: - return total_gas_cost - except ValueError: - raise OutOfGasError - - -def calculate_gas_extend_memory( - memory: bytearray, extensions: List[Tuple[U256, U256]] -) -> ExtendMemory: - """ - Calculates the gas amount to extend memory - - Parameters - ---------- - memory : - Memory contents of the EVM. - extensions: - List of extensions to be made to the memory. - Consists of a tuple of start position and size. - - Returns - ------- - extend_memory: `ExtendMemory` - """ - size_to_extend = Uint(0) - to_be_paid = Uint(0) - current_size = Uint(len(memory)) - for start_position, size in extensions: - if size == 0: - continue - before_size = ceil32(current_size) - after_size = ceil32(Uint(start_position) + Uint(size)) - if after_size <= before_size: - continue - - size_to_extend += after_size - before_size - already_paid = calculate_memory_gas_cost(before_size) - total_cost = calculate_memory_gas_cost(after_size) - to_be_paid += total_cost - already_paid - - current_size = after_size - - return ExtendMemory(to_be_paid, size_to_extend) - - -def calculate_message_call_gas( - value: U256, - gas: Uint, - gas_left: Uint, - memory_cost: Uint, - extra_gas: Uint, - call_stipend: Uint = GAS_CALL_STIPEND, -) -> MessageCallGas: - """ - Calculates the MessageCallGas (cost and stipend) for - executing call Opcodes. - - Parameters - ---------- - value: - The amount of `ETH` that needs to be transferred. - gas : - The amount of gas provided to the message-call. - gas_left : - The amount of gas left in the current frame. - memory_cost : - The amount needed to extend the memory in the current frame. - extra_gas : - The amount of gas needed for transferring value + creating a new - account inside a message call. - call_stipend : - The amount of stipend provided to a message call to execute code while - transferring value(ETH). - - Returns - ------- - message_call_gas: `MessageCallGas` - """ - call_stipend = Uint(0) if value == 0 else call_stipend - if gas_left < extra_gas + memory_cost: - return MessageCallGas(gas + extra_gas, gas + call_stipend) - - gas = min(gas, max_message_call_gas(gas_left - memory_cost - extra_gas)) - - return MessageCallGas(gas + extra_gas, gas + call_stipend) - - -def max_message_call_gas(gas: Uint) -> Uint: - """ - Calculates the maximum gas that is allowed for making a message call - - Parameters - ---------- - gas : - The amount of gas provided to the message-call. - - Returns - ------- - max_allowed_message_call_gas: `ethereum.base_types.Uint` - The maximum gas allowed for making the message-call. - """ - return gas - (gas // Uint(64)) - - -def init_code_cost(init_code_length: Uint) -> Uint: - """ - Calculates the gas to be charged for the init code in CREAT* - opcodes as well as create transactions. - - Parameters - ---------- - init_code_length : - The length of the init code provided to the opcode - or a create transaction - - Returns - ------- - init_code_gas: `ethereum.base_types.Uint` - The gas to be charged for the init code. - """ - return GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) // Uint(32) - - -def calculate_excess_blob_gas(parent_header: Header) -> U64: - """ - Calculated the excess blob gas for the current block based - on the gas used in the parent block. - - Parameters - ---------- - parent_header : - The parent block of the current block. - - Returns - ------- - excess_blob_gas: `ethereum.base_types.U64` - The excess blob gas for the current block. - """ - # At the fork block, these are defined as zero. - excess_blob_gas = U64(0) - blob_gas_used = U64(0) - - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. - excess_blob_gas = parent_header.excess_blob_gas - blob_gas_used = parent_header.blob_gas_used - - parent_blob_gas = excess_blob_gas + blob_gas_used - if parent_blob_gas < TARGET_BLOB_GAS_PER_BLOCK: - return U64(0) - else: - return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK - - -def calculate_total_blob_gas(tx: Transaction) -> Uint: - """ - Calculate the total blob gas for a transaction. - - Parameters - ---------- - tx : - The transaction for which the blob gas is to be calculated. - - Returns - ------- - total_blob_gas: `ethereum.base_types.Uint` - The total blob gas for the transaction. - """ - if isinstance(tx, BlobTransaction): - return GAS_PER_BLOB * Uint(len(tx.blob_versioned_hashes)) - else: - return Uint(0) - - -def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: - """ - Calculate the blob gasprice for a block. - - Parameters - ---------- - excess_blob_gas : - The excess blob gas for the block. - - Returns - ------- - blob_gasprice: `Uint` - The blob gasprice. - """ - return taylor_exponential( - MIN_BLOB_GASPRICE, - Uint(excess_blob_gas), - BLOB_BASE_FEE_UPDATE_FRACTION, - ) - - -def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: - """ - Calculate the blob data fee for a transaction. - - Parameters - ---------- - excess_blob_gas : - The excess_blob_gas for the execution. - tx : - The transaction for which the blob data fee is to be calculated. - - Returns - ------- - data_fee: `Uint` - The blob data fee. - """ - return calculate_total_blob_gas(tx) * calculate_blob_gas_price( - excess_blob_gas - ) diff --git a/src/ethereum/osaka/vm/instructions/__init__.py b/src/ethereum/osaka/vm/instructions/__init__.py deleted file mode 100644 index b220581c72..0000000000 --- a/src/ethereum/osaka/vm/instructions/__init__.py +++ /dev/null @@ -1,366 +0,0 @@ -""" -EVM Instruction Encoding (Opcodes) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Machine readable representations of EVM instructions, and a mapping to their -implementations. -""" - -import enum -from typing import Callable, Dict - -from . import arithmetic as arithmetic_instructions -from . import bitwise as bitwise_instructions -from . import block as block_instructions -from . import comparison as comparison_instructions -from . import control_flow as control_flow_instructions -from . import environment as environment_instructions -from . import keccak as keccak_instructions -from . import log as log_instructions -from . import memory as memory_instructions -from . import stack as stack_instructions -from . import storage as storage_instructions -from . import system as system_instructions - - -class Ops(enum.Enum): - """ - Enum for EVM Opcodes - """ - - # Arithmetic Ops - ADD = 0x01 - MUL = 0x02 - SUB = 0x03 - DIV = 0x04 - SDIV = 0x05 - MOD = 0x06 - SMOD = 0x07 - ADDMOD = 0x08 - MULMOD = 0x09 - EXP = 0x0A - SIGNEXTEND = 0x0B - - # Comparison Ops - LT = 0x10 - GT = 0x11 - SLT = 0x12 - SGT = 0x13 - EQ = 0x14 - ISZERO = 0x15 - - # Bitwise Ops - AND = 0x16 - OR = 0x17 - XOR = 0x18 - NOT = 0x19 - BYTE = 0x1A - SHL = 0x1B - SHR = 0x1C - SAR = 0x1D - - # Keccak Op - KECCAK = 0x20 - - # Environmental Ops - ADDRESS = 0x30 - BALANCE = 0x31 - ORIGIN = 0x32 - CALLER = 0x33 - CALLVALUE = 0x34 - CALLDATALOAD = 0x35 - CALLDATASIZE = 0x36 - CALLDATACOPY = 0x37 - CODESIZE = 0x38 - CODECOPY = 0x39 - GASPRICE = 0x3A - EXTCODESIZE = 0x3B - EXTCODECOPY = 0x3C - RETURNDATASIZE = 0x3D - RETURNDATACOPY = 0x3E - EXTCODEHASH = 0x3F - - # Block Ops - BLOCKHASH = 0x40 - COINBASE = 0x41 - TIMESTAMP = 0x42 - NUMBER = 0x43 - PREVRANDAO = 0x44 - GASLIMIT = 0x45 - CHAINID = 0x46 - SELFBALANCE = 0x47 - BASEFEE = 0x48 - BLOBHASH = 0x49 - BLOBBASEFEE = 0x4A - - # Control Flow Ops - STOP = 0x00 - JUMP = 0x56 - JUMPI = 0x57 - PC = 0x58 - GAS = 0x5A - JUMPDEST = 0x5B - - # Storage Ops - SLOAD = 0x54 - SSTORE = 0x55 - TLOAD = 0x5C - TSTORE = 0x5D - - # Pop Operation - POP = 0x50 - - # Push Operations - PUSH0 = 0x5F - PUSH1 = 0x60 - PUSH2 = 0x61 - PUSH3 = 0x62 - PUSH4 = 0x63 - PUSH5 = 0x64 - PUSH6 = 0x65 - PUSH7 = 0x66 - PUSH8 = 0x67 - PUSH9 = 0x68 - PUSH10 = 0x69 - PUSH11 = 0x6A - PUSH12 = 0x6B - PUSH13 = 0x6C - PUSH14 = 0x6D - PUSH15 = 0x6E - PUSH16 = 0x6F - PUSH17 = 0x70 - PUSH18 = 0x71 - PUSH19 = 0x72 - PUSH20 = 0x73 - PUSH21 = 0x74 - PUSH22 = 0x75 - PUSH23 = 0x76 - PUSH24 = 0x77 - PUSH25 = 0x78 - PUSH26 = 0x79 - PUSH27 = 0x7A - PUSH28 = 0x7B - PUSH29 = 0x7C - PUSH30 = 0x7D - PUSH31 = 0x7E - PUSH32 = 0x7F - - # Dup operations - DUP1 = 0x80 - DUP2 = 0x81 - DUP3 = 0x82 - DUP4 = 0x83 - DUP5 = 0x84 - DUP6 = 0x85 - DUP7 = 0x86 - DUP8 = 0x87 - DUP9 = 0x88 - DUP10 = 0x89 - DUP11 = 0x8A - DUP12 = 0x8B - DUP13 = 0x8C - DUP14 = 0x8D - DUP15 = 0x8E - DUP16 = 0x8F - - # Swap operations - SWAP1 = 0x90 - SWAP2 = 0x91 - SWAP3 = 0x92 - SWAP4 = 0x93 - SWAP5 = 0x94 - SWAP6 = 0x95 - SWAP7 = 0x96 - SWAP8 = 0x97 - SWAP9 = 0x98 - SWAP10 = 0x99 - SWAP11 = 0x9A - SWAP12 = 0x9B - SWAP13 = 0x9C - SWAP14 = 0x9D - SWAP15 = 0x9E - SWAP16 = 0x9F - - # Memory Operations - MLOAD = 0x51 - MSTORE = 0x52 - MSTORE8 = 0x53 - MSIZE = 0x59 - MCOPY = 0x5E - - # Log Operations - LOG0 = 0xA0 - LOG1 = 0xA1 - LOG2 = 0xA2 - LOG3 = 0xA3 - LOG4 = 0xA4 - - # System Operations - CREATE = 0xF0 - CALL = 0xF1 - CALLCODE = 0xF2 - RETURN = 0xF3 - DELEGATECALL = 0xF4 - CREATE2 = 0xF5 - STATICCALL = 0xFA - REVERT = 0xFD - SELFDESTRUCT = 0xFF - - -op_implementation: Dict[Ops, Callable] = { - Ops.STOP: control_flow_instructions.stop, - Ops.ADD: arithmetic_instructions.add, - Ops.MUL: arithmetic_instructions.mul, - Ops.SUB: arithmetic_instructions.sub, - Ops.DIV: arithmetic_instructions.div, - Ops.SDIV: arithmetic_instructions.sdiv, - Ops.MOD: arithmetic_instructions.mod, - Ops.SMOD: arithmetic_instructions.smod, - Ops.ADDMOD: arithmetic_instructions.addmod, - Ops.MULMOD: arithmetic_instructions.mulmod, - Ops.EXP: arithmetic_instructions.exp, - Ops.SIGNEXTEND: arithmetic_instructions.signextend, - Ops.LT: comparison_instructions.less_than, - Ops.GT: comparison_instructions.greater_than, - Ops.SLT: comparison_instructions.signed_less_than, - Ops.SGT: comparison_instructions.signed_greater_than, - Ops.EQ: comparison_instructions.equal, - Ops.ISZERO: comparison_instructions.is_zero, - Ops.AND: bitwise_instructions.bitwise_and, - Ops.OR: bitwise_instructions.bitwise_or, - Ops.XOR: bitwise_instructions.bitwise_xor, - Ops.NOT: bitwise_instructions.bitwise_not, - Ops.BYTE: bitwise_instructions.get_byte, - Ops.SHL: bitwise_instructions.bitwise_shl, - Ops.SHR: bitwise_instructions.bitwise_shr, - Ops.SAR: bitwise_instructions.bitwise_sar, - Ops.KECCAK: keccak_instructions.keccak, - Ops.SLOAD: storage_instructions.sload, - Ops.BLOCKHASH: block_instructions.block_hash, - Ops.COINBASE: block_instructions.coinbase, - Ops.TIMESTAMP: block_instructions.timestamp, - Ops.NUMBER: block_instructions.number, - Ops.PREVRANDAO: block_instructions.prev_randao, - Ops.GASLIMIT: block_instructions.gas_limit, - Ops.CHAINID: block_instructions.chain_id, - Ops.MLOAD: memory_instructions.mload, - Ops.MSTORE: memory_instructions.mstore, - Ops.MSTORE8: memory_instructions.mstore8, - Ops.MSIZE: memory_instructions.msize, - Ops.MCOPY: memory_instructions.mcopy, - Ops.ADDRESS: environment_instructions.address, - Ops.BALANCE: environment_instructions.balance, - Ops.ORIGIN: environment_instructions.origin, - Ops.CALLER: environment_instructions.caller, - Ops.CALLVALUE: environment_instructions.callvalue, - Ops.CALLDATALOAD: environment_instructions.calldataload, - Ops.CALLDATASIZE: environment_instructions.calldatasize, - Ops.CALLDATACOPY: environment_instructions.calldatacopy, - Ops.CODESIZE: environment_instructions.codesize, - Ops.CODECOPY: environment_instructions.codecopy, - Ops.GASPRICE: environment_instructions.gasprice, - Ops.EXTCODESIZE: environment_instructions.extcodesize, - Ops.EXTCODECOPY: environment_instructions.extcodecopy, - Ops.RETURNDATASIZE: environment_instructions.returndatasize, - Ops.RETURNDATACOPY: environment_instructions.returndatacopy, - Ops.EXTCODEHASH: environment_instructions.extcodehash, - Ops.SELFBALANCE: environment_instructions.self_balance, - Ops.BASEFEE: environment_instructions.base_fee, - Ops.BLOBHASH: environment_instructions.blob_hash, - Ops.BLOBBASEFEE: environment_instructions.blob_base_fee, - Ops.SSTORE: storage_instructions.sstore, - Ops.TLOAD: storage_instructions.tload, - Ops.TSTORE: storage_instructions.tstore, - Ops.JUMP: control_flow_instructions.jump, - Ops.JUMPI: control_flow_instructions.jumpi, - Ops.PC: control_flow_instructions.pc, - Ops.GAS: control_flow_instructions.gas_left, - Ops.JUMPDEST: control_flow_instructions.jumpdest, - Ops.POP: stack_instructions.pop, - Ops.PUSH0: stack_instructions.push0, - Ops.PUSH1: stack_instructions.push1, - Ops.PUSH2: stack_instructions.push2, - Ops.PUSH3: stack_instructions.push3, - Ops.PUSH4: stack_instructions.push4, - Ops.PUSH5: stack_instructions.push5, - Ops.PUSH6: stack_instructions.push6, - Ops.PUSH7: stack_instructions.push7, - Ops.PUSH8: stack_instructions.push8, - Ops.PUSH9: stack_instructions.push9, - Ops.PUSH10: stack_instructions.push10, - Ops.PUSH11: stack_instructions.push11, - Ops.PUSH12: stack_instructions.push12, - Ops.PUSH13: stack_instructions.push13, - Ops.PUSH14: stack_instructions.push14, - Ops.PUSH15: stack_instructions.push15, - Ops.PUSH16: stack_instructions.push16, - Ops.PUSH17: stack_instructions.push17, - Ops.PUSH18: stack_instructions.push18, - Ops.PUSH19: stack_instructions.push19, - Ops.PUSH20: stack_instructions.push20, - Ops.PUSH21: stack_instructions.push21, - Ops.PUSH22: stack_instructions.push22, - Ops.PUSH23: stack_instructions.push23, - Ops.PUSH24: stack_instructions.push24, - Ops.PUSH25: stack_instructions.push25, - Ops.PUSH26: stack_instructions.push26, - Ops.PUSH27: stack_instructions.push27, - Ops.PUSH28: stack_instructions.push28, - Ops.PUSH29: stack_instructions.push29, - Ops.PUSH30: stack_instructions.push30, - Ops.PUSH31: stack_instructions.push31, - Ops.PUSH32: stack_instructions.push32, - Ops.DUP1: stack_instructions.dup1, - Ops.DUP2: stack_instructions.dup2, - Ops.DUP3: stack_instructions.dup3, - Ops.DUP4: stack_instructions.dup4, - Ops.DUP5: stack_instructions.dup5, - Ops.DUP6: stack_instructions.dup6, - Ops.DUP7: stack_instructions.dup7, - Ops.DUP8: stack_instructions.dup8, - Ops.DUP9: stack_instructions.dup9, - Ops.DUP10: stack_instructions.dup10, - Ops.DUP11: stack_instructions.dup11, - Ops.DUP12: stack_instructions.dup12, - Ops.DUP13: stack_instructions.dup13, - Ops.DUP14: stack_instructions.dup14, - Ops.DUP15: stack_instructions.dup15, - Ops.DUP16: stack_instructions.dup16, - Ops.SWAP1: stack_instructions.swap1, - Ops.SWAP2: stack_instructions.swap2, - Ops.SWAP3: stack_instructions.swap3, - Ops.SWAP4: stack_instructions.swap4, - Ops.SWAP5: stack_instructions.swap5, - Ops.SWAP6: stack_instructions.swap6, - Ops.SWAP7: stack_instructions.swap7, - Ops.SWAP8: stack_instructions.swap8, - Ops.SWAP9: stack_instructions.swap9, - Ops.SWAP10: stack_instructions.swap10, - Ops.SWAP11: stack_instructions.swap11, - Ops.SWAP12: stack_instructions.swap12, - Ops.SWAP13: stack_instructions.swap13, - Ops.SWAP14: stack_instructions.swap14, - Ops.SWAP15: stack_instructions.swap15, - Ops.SWAP16: stack_instructions.swap16, - Ops.LOG0: log_instructions.log0, - Ops.LOG1: log_instructions.log1, - Ops.LOG2: log_instructions.log2, - Ops.LOG3: log_instructions.log3, - Ops.LOG4: log_instructions.log4, - Ops.CREATE: system_instructions.create, - Ops.RETURN: system_instructions.return_, - Ops.CALL: system_instructions.call, - Ops.CALLCODE: system_instructions.callcode, - Ops.DELEGATECALL: system_instructions.delegatecall, - Ops.SELFDESTRUCT: system_instructions.selfdestruct, - Ops.STATICCALL: system_instructions.staticcall, - Ops.REVERT: system_instructions.revert, - Ops.CREATE2: system_instructions.create2, -} diff --git a/src/ethereum/osaka/vm/instructions/arithmetic.py b/src/ethereum/osaka/vm/instructions/arithmetic.py deleted file mode 100644 index 0b8df99543..0000000000 --- a/src/ethereum/osaka/vm/instructions/arithmetic.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Arithmetic Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM Arithmetic instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from ethereum.utils.numeric import get_sign - -from .. import Evm -from ..gas import ( - GAS_EXPONENTIATION, - GAS_EXPONENTIATION_PER_BYTE, - GAS_LOW, - GAS_MID, - GAS_VERY_LOW, - charge_gas, -) -from ..stack import pop, push - - -def add(evm: Evm) -> None: - """ - Adds the top two elements of the stack together, and pushes the result back - on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = x.wrapping_add(y) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def sub(evm: Evm) -> None: - """ - Subtracts the top two elements of the stack, and pushes the result back - on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = x.wrapping_sub(y) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mul(evm: Evm) -> None: - """ - Multiply the top two elements of the stack, and pushes the result back - on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - result = x.wrapping_mul(y) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def div(evm: Evm) -> None: - """ - Integer division of the top two elements of the stack. Pushes the result - back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - dividend = pop(evm.stack) - divisor = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - if divisor == 0: - quotient = U256(0) - else: - quotient = dividend // divisor - - push(evm.stack, quotient) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -U255_CEIL_VALUE = 2**255 - - -def sdiv(evm: Evm) -> None: - """ - Signed integer division of the top two elements of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - dividend = pop(evm.stack).to_signed() - divisor = pop(evm.stack).to_signed() - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - if divisor == 0: - quotient = 0 - elif dividend == -U255_CEIL_VALUE and divisor == -1: - quotient = -U255_CEIL_VALUE - else: - sign = get_sign(dividend * divisor) - quotient = sign * (abs(dividend) // abs(divisor)) - - push(evm.stack, U256.from_signed(quotient)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mod(evm: Evm) -> None: - """ - Modulo remainder of the top two elements of the stack. Pushes the result - back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - if y == 0: - remainder = U256(0) - else: - remainder = x % y - - push(evm.stack, remainder) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def smod(evm: Evm) -> None: - """ - Signed modulo remainder of the top two elements of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack).to_signed() - y = pop(evm.stack).to_signed() - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - if y == 0: - remainder = 0 - else: - remainder = get_sign(x) * (abs(x) % abs(y)) - - push(evm.stack, U256.from_signed(remainder)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def addmod(evm: Evm) -> None: - """ - Modulo addition of the top 2 elements with the 3rd element. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = Uint(pop(evm.stack)) - y = Uint(pop(evm.stack)) - z = Uint(pop(evm.stack)) - - # GAS - charge_gas(evm, GAS_MID) - - # OPERATION - if z == 0: - result = U256(0) - else: - result = U256((x + y) % z) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mulmod(evm: Evm) -> None: - """ - Modulo multiplication of the top 2 elements with the 3rd element. Pushes - the result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = Uint(pop(evm.stack)) - y = Uint(pop(evm.stack)) - z = Uint(pop(evm.stack)) - - # GAS - charge_gas(evm, GAS_MID) - - # OPERATION - if z == 0: - result = U256(0) - else: - result = U256((x * y) % z) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def exp(evm: Evm) -> None: - """ - Exponential operation of the top 2 elements. Pushes the result back on - the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - base = Uint(pop(evm.stack)) - exponent = Uint(pop(evm.stack)) - - # GAS - # This is equivalent to 1 + floor(log(y, 256)). But in python the log - # function is inaccurate leading to wrong results. - exponent_bits = exponent.bit_length() - exponent_bytes = (exponent_bits + Uint(7)) // Uint(8) - charge_gas( - evm, GAS_EXPONENTIATION + GAS_EXPONENTIATION_PER_BYTE * exponent_bytes - ) - - # OPERATION - result = U256(pow(base, exponent, Uint(U256.MAX_VALUE) + Uint(1))) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def signextend(evm: Evm) -> None: - """ - Sign extend operation. In other words, extend a signed number which - fits in N bytes to 32 bytes. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - byte_num = pop(evm.stack) - value = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_LOW) - - # OPERATION - if byte_num > U256(31): - # Can't extend any further - result = value - else: - # U256(0).to_be_bytes() gives b'' instead b'\x00'. - value_bytes = bytes(value.to_be_bytes32()) - # Now among the obtained value bytes, consider only - # N `least significant bytes`, where N is `byte_num + 1`. - value_bytes = value_bytes[31 - int(byte_num) :] - sign_bit = value_bytes[0] >> 7 - if sign_bit == 0: - result = U256.from_be_bytes(value_bytes) - else: - num_bytes_prepend = U256(32) - (byte_num + U256(1)) - result = U256.from_be_bytes( - bytearray([0xFF] * num_bytes_prepend) + value_bytes - ) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/bitwise.py b/src/ethereum/osaka/vm/instructions/bitwise.py deleted file mode 100644 index 3abb58be48..0000000000 --- a/src/ethereum/osaka/vm/instructions/bitwise.py +++ /dev/null @@ -1,240 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Bitwise Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM bitwise instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from .. import Evm -from ..gas import GAS_VERY_LOW, charge_gas -from ..stack import pop, push - - -def bitwise_and(evm: Evm) -> None: - """ - Bitwise AND operation of the top 2 elements of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - push(evm.stack, x & y) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_or(evm: Evm) -> None: - """ - Bitwise OR operation of the top 2 elements of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - push(evm.stack, x | y) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_xor(evm: Evm) -> None: - """ - Bitwise XOR operation of the top 2 elements of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - y = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - push(evm.stack, x ^ y) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_not(evm: Evm) -> None: - """ - Bitwise NOT operation of the top element of the stack. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - push(evm.stack, ~x) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def get_byte(evm: Evm) -> None: - """ - For a word (defined by next top element of the stack), retrieve the - Nth byte (0-indexed and defined by top element of stack) from the - left (most significant) to right (least significant). - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - byte_index = pop(evm.stack) - word = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - if byte_index >= U256(32): - result = U256(0) - else: - extra_bytes_to_right = U256(31) - byte_index - # Remove the extra bytes in the right - word = word >> (extra_bytes_to_right * U256(8)) - # Remove the extra bytes in the left - word = word & U256(0xFF) - result = word - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_shl(evm: Evm) -> None: - """ - Logical shift left (SHL) operation of the top 2 elements of the stack. - Pushes the result back on the stack. - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - shift = Uint(pop(evm.stack)) - value = Uint(pop(evm.stack)) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - if shift < Uint(256): - result = U256((value << shift) & Uint(U256.MAX_VALUE)) - else: - result = U256(0) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_shr(evm: Evm) -> None: - """ - Logical shift right (SHR) operation of the top 2 elements of the stack. - Pushes the result back on the stack. - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - shift = pop(evm.stack) - value = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - if shift < U256(256): - result = value >> shift - else: - result = U256(0) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def bitwise_sar(evm: Evm) -> None: - """ - Arithmetic shift right (SAR) operation of the top 2 elements of the stack. - Pushes the result back on the stack. - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - shift = int(pop(evm.stack)) - signed_value = pop(evm.stack).to_signed() - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - if shift < 256: - result = U256.from_signed(signed_value >> shift) - elif signed_value >= 0: - result = U256(0) - else: - result = U256.MAX_VALUE - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/block.py b/src/ethereum/osaka/vm/instructions/block.py deleted file mode 100644 index 80644000fd..0000000000 --- a/src/ethereum/osaka/vm/instructions/block.py +++ /dev/null @@ -1,255 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Block Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM block instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from .. import Evm -from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas -from ..stack import pop, push - - -def block_hash(evm: Evm) -> None: - """ - Push the hash of one of the 256 most recent complete blocks onto the - stack. The block number to hash is present at the top of the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackUnderflowError` - If `len(stack)` is less than `1`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `20`. - """ - # STACK - block_number = Uint(pop(evm.stack)) - - # GAS - charge_gas(evm, GAS_BLOCK_HASH) - - # OPERATION - max_block_number = block_number + Uint(256) - current_block_number = evm.message.block_env.number - if ( - current_block_number <= block_number - or current_block_number > max_block_number - ): - # Default hash to 0, if the block of interest is not yet on the chain - # (including the block which has the current executing transaction), - # or if the block's age is more than 256. - hash = b"\x00" - else: - hash = evm.message.block_env.block_hashes[ - -(current_block_number - block_number) - ] - - push(evm.stack, U256.from_be_bytes(hash)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def coinbase(evm: Evm) -> None: - """ - Push the current block's beneficiary address (address of the block miner) - onto the stack. - - Here the current block refers to the block in which the currently - executing transaction/call resides. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def timestamp(evm: Evm) -> None: - """ - Push the current block's timestamp onto the stack. Here the timestamp - being referred is actually the unix timestamp in seconds. - - Here the current block refers to the block in which the currently - executing transaction/call resides. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, evm.message.block_env.time) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def number(evm: Evm) -> None: - """ - Push the current block's number onto the stack. - - Here the current block refers to the block in which the currently - executing transaction/call resides. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.message.block_env.number)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def prev_randao(evm: Evm) -> None: - """ - Push the `prev_randao` value onto the stack. - - The `prev_randao` value is the random output of the beacon chain's - randomness oracle for the previous block. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def gas_limit(evm: Evm) -> None: - """ - Push the current block's gas limit onto the stack. - - Here the current block refers to the block in which the currently - executing transaction/call resides. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.message.block_env.block_gas_limit)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def chain_id(evm: Evm) -> None: - """ - Push the chain id onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` - If `len(stack)` is equal to `1024`. - :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` - If `evm.gas_left` is less than `2`. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.message.block_env.chain_id)) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/comparison.py b/src/ethereum/osaka/vm/instructions/comparison.py deleted file mode 100644 index 275455ba53..0000000000 --- a/src/ethereum/osaka/vm/instructions/comparison.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Comparison Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM Comparison instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from .. import Evm -from ..gas import GAS_VERY_LOW, charge_gas -from ..stack import pop, push - - -def less_than(evm: Evm) -> None: - """ - Checks if the top element is less than the next top element. Pushes the - result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - left = pop(evm.stack) - right = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(left < right) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def signed_less_than(evm: Evm) -> None: - """ - Signed less-than comparison. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - left = pop(evm.stack).to_signed() - right = pop(evm.stack).to_signed() - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(left < right) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def greater_than(evm: Evm) -> None: - """ - Checks if the top element is greater than the next top element. Pushes - the result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - left = pop(evm.stack) - right = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(left > right) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def signed_greater_than(evm: Evm) -> None: - """ - Signed greater-than comparison. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - left = pop(evm.stack).to_signed() - right = pop(evm.stack).to_signed() - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(left > right) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def equal(evm: Evm) -> None: - """ - Checks if the top element is equal to the next top element. Pushes - the result back on the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - left = pop(evm.stack) - right = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(left == right) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def is_zero(evm: Evm) -> None: - """ - Checks if the top element is equal to 0. Pushes the result back on the - stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - x = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - result = U256(x == 0) - - push(evm.stack, result) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/control_flow.py b/src/ethereum/osaka/vm/instructions/control_flow.py deleted file mode 100644 index 7722661f79..0000000000 --- a/src/ethereum/osaka/vm/instructions/control_flow.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Control Flow Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM control flow instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from ...vm.gas import GAS_BASE, GAS_HIGH, GAS_JUMPDEST, GAS_MID, charge_gas -from .. import Evm -from ..exceptions import InvalidJumpDestError -from ..stack import pop, push - - -def stop(evm: Evm) -> None: - """ - Stop further execution of EVM code. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - pass - - # GAS - pass - - # OPERATION - evm.running = False - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def jump(evm: Evm) -> None: - """ - Alter the program counter to the location specified by the top of the - stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - jump_dest = Uint(pop(evm.stack)) - - # GAS - charge_gas(evm, GAS_MID) - - # OPERATION - if jump_dest not in evm.valid_jump_destinations: - raise InvalidJumpDestError - - # PROGRAM COUNTER - evm.pc = Uint(jump_dest) - - -def jumpi(evm: Evm) -> None: - """ - Alter the program counter to the specified location if and only if a - condition is true. If the condition is not true, then the program counter - would increase only by 1. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - jump_dest = Uint(pop(evm.stack)) - conditional_value = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_HIGH) - - # OPERATION - if conditional_value == 0: - destination = evm.pc + Uint(1) - elif jump_dest not in evm.valid_jump_destinations: - raise InvalidJumpDestError - else: - destination = jump_dest - - # PROGRAM COUNTER - evm.pc = destination - - -def pc(evm: Evm) -> None: - """ - Push onto the stack the value of the program counter after reaching the - current instruction and without increasing it for the next instruction. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.pc)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def gas_left(evm: Evm) -> None: - """ - Push the amount of available gas (including the corresponding reduction - for the cost of this instruction) onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.gas_left)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def jumpdest(evm: Evm) -> None: - """ - Mark a valid destination for jumps. This is a noop, present only - to be used by `JUMP` and `JUMPI` opcodes to verify that their jump is - valid. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_JUMPDEST) - - # OPERATION - pass - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py deleted file mode 100644 index 5ddd12dac8..0000000000 --- a/src/ethereum/osaka/vm/instructions/environment.py +++ /dev/null @@ -1,597 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Environmental Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM environment related instructions. -""" - -from ethereum_types.bytes import Bytes32 -from ethereum_types.numeric import U256, Uint, ulen - -from ethereum.crypto.hash import keccak256 -from ethereum.utils.numeric import ceil32 - -from ...fork_types import EMPTY_ACCOUNT -from ...state import get_account -from ...utils.address import to_address -from ...vm.memory import buffer_read, memory_write -from .. import Evm -from ..exceptions import OutOfBoundsRead -from ..gas import ( - GAS_BASE, - GAS_BLOBHASH_OPCODE, - GAS_COLD_ACCOUNT_ACCESS, - GAS_COPY, - GAS_FAST_STEP, - GAS_RETURN_DATA_COPY, - GAS_VERY_LOW, - GAS_WARM_ACCESS, - calculate_blob_gas_price, - calculate_gas_extend_memory, - charge_gas, -) -from ..stack import pop, push - - -def address(evm: Evm) -> None: - """ - Pushes the address of the current executing account to the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.current_target)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def balance(evm: Evm) -> None: - """ - Pushes the balance of the given account onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - address = to_address(pop(evm.stack)) - - # GAS - if address in evm.accessed_addresses: - charge_gas(evm, GAS_WARM_ACCESS) - else: - evm.accessed_addresses.add(address) - charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) - - # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account(evm.message.block_env.state, address).balance - - push(evm.stack, balance) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def origin(evm: Evm) -> None: - """ - Pushes the address of the original transaction sender to the stack. - The origin address can only be an EOA. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def caller(evm: Evm) -> None: - """ - Pushes the address of the caller onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.caller)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def callvalue(evm: Evm) -> None: - """ - Push the value (in wei) sent with the call onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, evm.message.value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def calldataload(evm: Evm) -> None: - """ - Push a word (32 bytes) of the input data belonging to the current - environment onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - start_index = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - value = buffer_read(evm.message.data, start_index, U256(32)) - - push(evm.stack, U256.from_be_bytes(value)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def calldatasize(evm: Evm) -> None: - """ - Push the size of input data in current environment onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(len(evm.message.data))) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def calldatacopy(evm: Evm) -> None: - """ - Copy a portion of the input data in current environment to memory. - - This will also expand the memory, in case that the memory is insufficient - to store the data. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - memory_start_index = pop(evm.stack) - data_start_index = pop(evm.stack) - size = pop(evm.stack) - - # GAS - words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GAS_COPY * words - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - value = buffer_read(evm.message.data, data_start_index, size) - memory_write(evm.memory, memory_start_index, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def codesize(evm: Evm) -> None: - """ - Push the size of code running in current environment onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(len(evm.code))) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def codecopy(evm: Evm) -> None: - """ - Copy a portion of the code in current environment to memory. - - This will also expand the memory, in case that the memory is insufficient - to store the data. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - memory_start_index = pop(evm.stack) - code_start_index = pop(evm.stack) - size = pop(evm.stack) - - # GAS - words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GAS_COPY * words - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - value = buffer_read(evm.code, code_start_index, size) - memory_write(evm.memory, memory_start_index, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def gasprice(evm: Evm) -> None: - """ - Push the gas price used in current environment onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.message.tx_env.gas_price)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def extcodesize(evm: Evm) -> None: - """ - Push the code size of a given account onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - address = to_address(pop(evm.stack)) - - # GAS - if address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - charge_gas(evm, access_gas_cost) - - # OPERATION - code = get_account(evm.message.block_env.state, address).code - - codesize = U256(len(code)) - push(evm.stack, codesize) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def extcodecopy(evm: Evm) -> None: - """ - Copy a portion of an account's code to memory. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - address = to_address(pop(evm.stack)) - memory_start_index = pop(evm.stack) - code_start_index = pop(evm.stack) - size = pop(evm.stack) - - # GAS - words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GAS_COPY * words - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - - if address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - code = get_account(evm.message.block_env.state, address).code - - value = buffer_read(code, code_start_index, size) - memory_write(evm.memory, memory_start_index, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def returndatasize(evm: Evm) -> None: - """ - Pushes the size of the return data buffer onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(len(evm.return_data))) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def returndatacopy(evm: Evm) -> None: - """ - Copies data from the return data buffer code to memory - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - memory_start_index = pop(evm.stack) - return_data_start_position = pop(evm.stack) - size = pop(evm.stack) - - # GAS - words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GAS_RETURN_DATA_COPY * words - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) - if Uint(return_data_start_position) + Uint(size) > ulen(evm.return_data): - raise OutOfBoundsRead - - evm.memory += b"\x00" * extend_memory.expand_by - value = evm.return_data[ - return_data_start_position : return_data_start_position + size - ] - memory_write(evm.memory, memory_start_index, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def extcodehash(evm: Evm) -> None: - """ - Returns the keccak256 hash of a contract’s bytecode - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - address = to_address(pop(evm.stack)) - - # GAS - if address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - charge_gas(evm, access_gas_cost) - - # OPERATION - account = get_account(evm.message.block_env.state, address) - - if account == EMPTY_ACCOUNT: - codehash = U256(0) - else: - code = account.code - codehash = U256.from_be_bytes(keccak256(code)) - - push(evm.stack, codehash) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def self_balance(evm: Evm) -> None: - """ - Pushes the balance of the current address to the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_FAST_STEP) - - # OPERATION - # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account( - evm.message.block_env.state, evm.message.current_target - ).balance - - push(evm.stack, balance) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def base_fee(evm: Evm) -> None: - """ - Pushes the base fee of the current block on to the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def blob_hash(evm: Evm) -> None: - """ - Pushes the versioned hash at a particular index on to the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - index = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_BLOBHASH_OPCODE) - - # OPERATION - if int(index) < len(evm.message.tx_env.blob_versioned_hashes): - blob_hash = evm.message.tx_env.blob_versioned_hashes[index] - else: - blob_hash = Bytes32(b"\x00" * 32) - push(evm.stack, U256.from_be_bytes(blob_hash)) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def blob_base_fee(evm: Evm) -> None: - """ - Pushes the blob base fee on to the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - blob_base_fee = calculate_blob_gas_price( - evm.message.block_env.excess_blob_gas - ) - push(evm.stack, U256(blob_base_fee)) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/keccak.py b/src/ethereum/osaka/vm/instructions/keccak.py deleted file mode 100644 index 830d368277..0000000000 --- a/src/ethereum/osaka/vm/instructions/keccak.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Keccak Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM keccak instructions. -""" - -from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.hash import keccak256 -from ethereum.utils.numeric import ceil32 - -from .. import Evm -from ..gas import ( - GAS_KECCAK256, - GAS_KECCAK256_WORD, - calculate_gas_extend_memory, - charge_gas, -) -from ..memory import memory_read_bytes -from ..stack import pop, push - - -def keccak(evm: Evm) -> None: - """ - Pushes to the stack the Keccak-256 hash of a region of memory. - - This also expands the memory, in case the memory is insufficient to - access the data's memory location. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - memory_start_index = pop(evm.stack) - size = pop(evm.stack) - - # GAS - words = ceil32(Uint(size)) // Uint(32) - word_gas_cost = GAS_KECCAK256_WORD * words - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - charge_gas(evm, GAS_KECCAK256 + word_gas_cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - data = memory_read_bytes(evm.memory, memory_start_index, size) - hash = keccak256(data) - - push(evm.stack, U256.from_be_bytes(hash)) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/log.py b/src/ethereum/osaka/vm/instructions/log.py deleted file mode 100644 index 87c06ed6be..0000000000 --- a/src/ethereum/osaka/vm/instructions/log.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Logging Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM logging instructions. -""" -from functools import partial - -from ethereum_types.numeric import Uint - -from ...blocks import Log -from .. import Evm -from ..exceptions import WriteInStaticContext -from ..gas import ( - GAS_LOG, - GAS_LOG_DATA, - GAS_LOG_TOPIC, - calculate_gas_extend_memory, - charge_gas, -) -from ..memory import memory_read_bytes -from ..stack import pop - - -def log_n(evm: Evm, num_topics: int) -> None: - """ - Appends a log entry, having `num_topics` topics, to the evm logs. - - This will also expand the memory if the data (required by the log entry) - corresponding to the memory is not accessible. - - Parameters - ---------- - evm : - The current EVM frame. - num_topics : - The number of topics to be included in the log entry. - - """ - # STACK - memory_start_index = pop(evm.stack) - size = pop(evm.stack) - - topics = [] - for _ in range(num_topics): - topic = pop(evm.stack).to_be_bytes32() - topics.append(topic) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - charge_gas( - evm, - GAS_LOG - + GAS_LOG_DATA * Uint(size) - + GAS_LOG_TOPIC * Uint(num_topics) - + extend_memory.cost, - ) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext - log_entry = Log( - address=evm.message.current_target, - topics=tuple(topics), - data=memory_read_bytes(evm.memory, memory_start_index, size), - ) - - evm.logs = evm.logs + (log_entry,) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -log0 = partial(log_n, num_topics=0) -log1 = partial(log_n, num_topics=1) -log2 = partial(log_n, num_topics=2) -log3 = partial(log_n, num_topics=3) -log4 = partial(log_n, num_topics=4) diff --git a/src/ethereum/osaka/vm/instructions/memory.py b/src/ethereum/osaka/vm/instructions/memory.py deleted file mode 100644 index 89533af37e..0000000000 --- a/src/ethereum/osaka/vm/instructions/memory.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Memory Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM Memory instructions. -""" -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U256, Uint - -from ethereum.utils.numeric import ceil32 - -from .. import Evm -from ..gas import ( - GAS_BASE, - GAS_COPY, - GAS_VERY_LOW, - calculate_gas_extend_memory, - charge_gas, -) -from ..memory import memory_read_bytes, memory_write -from ..stack import pop, push - - -def mstore(evm: Evm) -> None: - """ - Stores a word to memory. - This also expands the memory, if the memory is - insufficient to store the word. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - start_position = pop(evm.stack) - value = pop(evm.stack).to_be_bytes32() - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(start_position, U256(len(value)))] - ) - - charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - memory_write(evm.memory, start_position, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mstore8(evm: Evm) -> None: - """ - Stores a byte to memory. - This also expands the memory, if the memory is - insufficient to store the word. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - start_position = pop(evm.stack) - value = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(start_position, U256(1))] - ) - - charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - normalized_bytes_value = Bytes([value & U256(0xFF)]) - memory_write(evm.memory, start_position, normalized_bytes_value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mload(evm: Evm) -> None: - """ - Load word from memory. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - start_position = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(start_position, U256(32))] - ) - charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - value = U256.from_be_bytes( - memory_read_bytes(evm.memory, start_position, U256(32)) - ) - push(evm.stack, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def msize(evm: Evm) -> None: - """ - Push the size of active memory in bytes onto the stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - push(evm.stack, U256(len(evm.memory))) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def mcopy(evm: Evm) -> None: - """ - Copy the bytes in memory from one location to another. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - destination = pop(evm.stack) - source = pop(evm.stack) - length = pop(evm.stack) - - # GAS - words = ceil32(Uint(length)) // Uint(32) - copy_gas_cost = GAS_COPY * words - - extend_memory = calculate_gas_extend_memory( - evm.memory, [(source, length), (destination, length)] - ) - charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - value = memory_read_bytes(evm.memory, source, length) - memory_write(evm.memory, destination, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/stack.py b/src/ethereum/osaka/vm/instructions/stack.py deleted file mode 100644 index 2e8a492412..0000000000 --- a/src/ethereum/osaka/vm/instructions/stack.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Stack Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM stack related instructions. -""" - -from functools import partial - -from ethereum_types.numeric import U256, Uint - -from .. import Evm, stack -from ..exceptions import StackUnderflowError -from ..gas import GAS_BASE, GAS_VERY_LOW, charge_gas -from ..memory import buffer_read - - -def pop(evm: Evm) -> None: - """ - Remove item from stack. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - stack.pop(evm.stack) - - # GAS - charge_gas(evm, GAS_BASE) - - # OPERATION - pass - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def push_n(evm: Evm, num_bytes: int) -> None: - """ - Pushes a N-byte immediate onto the stack. Push zero if num_bytes is zero. - - Parameters - ---------- - evm : - The current EVM frame. - - num_bytes : - The number of immediate bytes to be read from the code and pushed to - the stack. Push zero if num_bytes is zero. - - """ - # STACK - pass - - # GAS - if num_bytes == 0: - charge_gas(evm, GAS_BASE) - else: - charge_gas(evm, GAS_VERY_LOW) - - # OPERATION - data_to_push = U256.from_be_bytes( - buffer_read(evm.code, U256(evm.pc + Uint(1)), U256(num_bytes)) - ) - stack.push(evm.stack, data_to_push) - - # PROGRAM COUNTER - evm.pc += Uint(1) + Uint(num_bytes) - - -def dup_n(evm: Evm, item_number: int) -> None: - """ - Duplicate the Nth stack item (from top of the stack) to the top of stack. - - Parameters - ---------- - evm : - The current EVM frame. - - item_number : - The stack item number (0-indexed from top of stack) to be duplicated - to the top of stack. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_VERY_LOW) - if item_number >= len(evm.stack): - raise StackUnderflowError - data_to_duplicate = evm.stack[len(evm.stack) - 1 - item_number] - stack.push(evm.stack, data_to_duplicate) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def swap_n(evm: Evm, item_number: int) -> None: - """ - Swap the top and the `item_number` element of the stack, where - the top of the stack is position zero. - - If `item_number` is zero, this function does nothing (which should not be - possible, since there is no `SWAP0` instruction). - - Parameters - ---------- - evm : - The current EVM frame. - - item_number : - The stack item number (0-indexed from top of stack) to be swapped - with the top of stack element. - - """ - # STACK - pass - - # GAS - charge_gas(evm, GAS_VERY_LOW) - if item_number >= len(evm.stack): - raise StackUnderflowError - evm.stack[-1], evm.stack[-1 - item_number] = ( - evm.stack[-1 - item_number], - evm.stack[-1], - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -push0 = partial(push_n, num_bytes=0) -push1 = partial(push_n, num_bytes=1) -push2 = partial(push_n, num_bytes=2) -push3 = partial(push_n, num_bytes=3) -push4 = partial(push_n, num_bytes=4) -push5 = partial(push_n, num_bytes=5) -push6 = partial(push_n, num_bytes=6) -push7 = partial(push_n, num_bytes=7) -push8 = partial(push_n, num_bytes=8) -push9 = partial(push_n, num_bytes=9) -push10 = partial(push_n, num_bytes=10) -push11 = partial(push_n, num_bytes=11) -push12 = partial(push_n, num_bytes=12) -push13 = partial(push_n, num_bytes=13) -push14 = partial(push_n, num_bytes=14) -push15 = partial(push_n, num_bytes=15) -push16 = partial(push_n, num_bytes=16) -push17 = partial(push_n, num_bytes=17) -push18 = partial(push_n, num_bytes=18) -push19 = partial(push_n, num_bytes=19) -push20 = partial(push_n, num_bytes=20) -push21 = partial(push_n, num_bytes=21) -push22 = partial(push_n, num_bytes=22) -push23 = partial(push_n, num_bytes=23) -push24 = partial(push_n, num_bytes=24) -push25 = partial(push_n, num_bytes=25) -push26 = partial(push_n, num_bytes=26) -push27 = partial(push_n, num_bytes=27) -push28 = partial(push_n, num_bytes=28) -push29 = partial(push_n, num_bytes=29) -push30 = partial(push_n, num_bytes=30) -push31 = partial(push_n, num_bytes=31) -push32 = partial(push_n, num_bytes=32) - -dup1 = partial(dup_n, item_number=0) -dup2 = partial(dup_n, item_number=1) -dup3 = partial(dup_n, item_number=2) -dup4 = partial(dup_n, item_number=3) -dup5 = partial(dup_n, item_number=4) -dup6 = partial(dup_n, item_number=5) -dup7 = partial(dup_n, item_number=6) -dup8 = partial(dup_n, item_number=7) -dup9 = partial(dup_n, item_number=8) -dup10 = partial(dup_n, item_number=9) -dup11 = partial(dup_n, item_number=10) -dup12 = partial(dup_n, item_number=11) -dup13 = partial(dup_n, item_number=12) -dup14 = partial(dup_n, item_number=13) -dup15 = partial(dup_n, item_number=14) -dup16 = partial(dup_n, item_number=15) - -swap1 = partial(swap_n, item_number=1) -swap2 = partial(swap_n, item_number=2) -swap3 = partial(swap_n, item_number=3) -swap4 = partial(swap_n, item_number=4) -swap5 = partial(swap_n, item_number=5) -swap6 = partial(swap_n, item_number=6) -swap7 = partial(swap_n, item_number=7) -swap8 = partial(swap_n, item_number=8) -swap9 = partial(swap_n, item_number=9) -swap10 = partial(swap_n, item_number=10) -swap11 = partial(swap_n, item_number=11) -swap12 = partial(swap_n, item_number=12) -swap13 = partial(swap_n, item_number=13) -swap14 = partial(swap_n, item_number=14) -swap15 = partial(swap_n, item_number=15) -swap16 = partial(swap_n, item_number=16) diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py deleted file mode 100644 index 65a0d5a9b6..0000000000 --- a/src/ethereum/osaka/vm/instructions/storage.py +++ /dev/null @@ -1,184 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Storage Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM storage related instructions. -""" -from ethereum_types.numeric import Uint - -from ...state import ( - get_storage, - get_storage_original, - get_transient_storage, - set_storage, - set_transient_storage, -) -from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext -from ..gas import ( - GAS_CALL_STIPEND, - GAS_COLD_SLOAD, - GAS_STORAGE_CLEAR_REFUND, - GAS_STORAGE_SET, - GAS_STORAGE_UPDATE, - GAS_WARM_ACCESS, - charge_gas, -) -from ..stack import pop, push - - -def sload(evm: Evm) -> None: - """ - Loads to the stack, the value corresponding to a certain key from the - storage of the current account. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - key = pop(evm.stack).to_be_bytes32() - - # GAS - if (evm.message.current_target, key) in evm.accessed_storage_keys: - charge_gas(evm, GAS_WARM_ACCESS) - else: - evm.accessed_storage_keys.add((evm.message.current_target, key)) - charge_gas(evm, GAS_COLD_SLOAD) - - # OPERATION - value = get_storage( - evm.message.block_env.state, evm.message.current_target, key - ) - - push(evm.stack, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def sstore(evm: Evm) -> None: - """ - Stores a value at a certain key in the current context's storage. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - # STACK - key = pop(evm.stack).to_be_bytes32() - new_value = pop(evm.stack) - 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 - ) - current_value = get_storage(state, evm.message.current_target, key) - - gas_cost = Uint(0) - - if (evm.message.current_target, key) not in evm.accessed_storage_keys: - evm.accessed_storage_keys.add((evm.message.current_target, key)) - gas_cost += GAS_COLD_SLOAD - - if original_value == current_value and current_value != new_value: - if original_value == 0: - gas_cost += GAS_STORAGE_SET - else: - gas_cost += GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - else: - gas_cost += GAS_WARM_ACCESS - - # Refund Counter Calculation - if current_value != new_value: - if original_value != 0 and current_value != 0 and new_value == 0: - # Storage is cleared for the first time in the transaction - evm.refund_counter += int(GAS_STORAGE_CLEAR_REFUND) - - if original_value != 0 and current_value == 0: - # Gas refund issued earlier to be reversed - evm.refund_counter -= int(GAS_STORAGE_CLEAR_REFUND) - - if original_value == new_value: - # Storage slot being restored to its original value - if original_value == 0: - # Slot was originally empty and was SET earlier - evm.refund_counter += int(GAS_STORAGE_SET - GAS_WARM_ACCESS) - else: - # Slot was originally non-empty and was UPDATED earlier - evm.refund_counter += int( - GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS - ) - - charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext - set_storage(state, evm.message.current_target, key, new_value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def tload(evm: Evm) -> None: - """ - Loads to the stack, the value corresponding to a certain key from the - transient storage of the current account. - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - key = pop(evm.stack).to_be_bytes32() - - # GAS - charge_gas(evm, GAS_WARM_ACCESS) - - # OPERATION - value = get_transient_storage( - evm.message.tx_env.transient_storage, evm.message.current_target, key - ) - push(evm.stack, value) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def tstore(evm: Evm) -> None: - """ - Stores a value at a certain key in the current context's transient storage. - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - key = pop(evm.stack).to_be_bytes32() - new_value = pop(evm.stack) - - # GAS - charge_gas(evm, GAS_WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext - set_transient_storage( - evm.message.tx_env.transient_storage, - 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 deleted file mode 100644 index 88ba466e8e..0000000000 --- a/src/ethereum/osaka/vm/instructions/system.py +++ /dev/null @@ -1,755 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) System Instructions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementations of the EVM system related instructions. -""" -from ethereum_types.bytes import Bytes0 -from ethereum_types.numeric import U256, Uint - -from ethereum.utils.numeric import ceil32 - -from ...fork_types import Address -from ...state import ( - account_exists_and_is_empty, - 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, - to_address, -) -from ...vm.eoa_delegation import access_delegation -from .. import ( - Evm, - Message, - incorporate_child_on_error, - incorporate_child_on_success, -) -from ..exceptions import OutOfGasError, Revert, WriteInStaticContext -from ..gas import ( - GAS_CALL_VALUE, - GAS_COLD_ACCOUNT_ACCESS, - GAS_CREATE, - GAS_KECCAK256_WORD, - GAS_NEW_ACCOUNT, - GAS_SELF_DESTRUCT, - GAS_SELF_DESTRUCT_NEW_ACCOUNT, - GAS_WARM_ACCESS, - GAS_ZERO, - calculate_gas_extend_memory, - calculate_message_call_gas, - charge_gas, - init_code_cost, - max_message_call_gas, -) -from ..memory import memory_read_bytes, memory_write -from ..stack import pop, push - - -def generic_create( - evm: Evm, - endowment: U256, - contract_address: Address, - memory_start_position: U256, - memory_size: U256, - init_code_gas: Uint, -) -> None: - """ - Core logic used by the `CREATE*` family of opcodes. - """ - # This import causes a circular import error - # if it's not moved inside this method - from ...vm.interpreter import ( - MAX_CODE_SIZE, - STACK_DEPTH_LIMIT, - process_create_message, - ) - - call_data = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - if len(call_data) > 2 * MAX_CODE_SIZE: - raise OutOfGasError - - evm.accessed_addresses.add(contract_address) - - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) - evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext - evm.return_data = b"" - - sender_address = evm.message.current_target - sender = get_account(evm.message.block_env.state, sender_address) - - if ( - sender.balance < endowment - or sender.nonce == Uint(2**64 - 1) - or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT - ): - evm.gas_left += create_message_gas - push(evm.stack, U256(0)) - return - - 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 - ) - push(evm.stack, U256(0)) - return - - increment_nonce(evm.message.block_env.state, evm.message.current_target) - - child_message = Message( - block_env=evm.message.block_env, - tx_env=evm.message.tx_env, - caller=evm.message.current_target, - target=Bytes0(), - gas=create_message_gas, - value=endowment, - data=b"", - code=call_data, - current_target=contract_address, - depth=evm.message.depth + Uint(1), - code_address=None, - should_transfer_value=True, - is_static=False, - accessed_addresses=evm.accessed_addresses.copy(), - accessed_storage_keys=evm.accessed_storage_keys.copy(), - parent_evm=evm, - ) - child_evm = process_create_message(child_message) - - if child_evm.error: - incorporate_child_on_error(evm, child_evm) - evm.return_data = child_evm.output - push(evm.stack, U256(0)) - else: - incorporate_child_on_success(evm, child_evm) - evm.return_data = b"" - push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) - - -def create(evm: Evm) -> None: - """ - Creates a new account with associated code. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - endowment = pop(evm.stack) - memory_start_position = pop(evm.stack) - memory_size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_position, memory_size)] - ) - init_code_gas = init_code_cost(Uint(memory_size)) - - charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) - - # OPERATION - 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, - ) - - generic_create( - evm, - endowment, - contract_address, - memory_start_position, - memory_size, - init_code_gas, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def create2(evm: Evm) -> None: - """ - Creates a new account with associated code. - - It's similar to CREATE opcode except that the address of new account - depends on the init_code instead of the nonce of sender. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - endowment = pop(evm.stack) - memory_start_position = pop(evm.stack) - memory_size = pop(evm.stack) - salt = pop(evm.stack).to_be_bytes32() - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_position, memory_size)] - ) - call_data_words = ceil32(Uint(memory_size)) // Uint(32) - init_code_gas = init_code_cost(Uint(memory_size)) - charge_gas( - evm, - GAS_CREATE - + GAS_KECCAK256_WORD * call_data_words - + extend_memory.cost - + init_code_gas, - ) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - contract_address = compute_create2_contract_address( - evm.message.current_target, - salt, - memory_read_bytes(evm.memory, memory_start_position, memory_size), - ) - - generic_create( - evm, - endowment, - contract_address, - memory_start_position, - memory_size, - init_code_gas, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def return_(evm: Evm) -> None: - """ - Halts execution returning output data. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - memory_start_position = pop(evm.stack) - memory_size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_position, memory_size)] - ) - - charge_gas(evm, GAS_ZERO + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - evm.output = memory_read_bytes( - evm.memory, memory_start_position, memory_size - ) - - evm.running = False - - # PROGRAM COUNTER - pass - - -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: bytes, - is_delegated_code: bool, -) -> None: - """ - Perform the core logic of the `CALL*` family of opcodes. - """ - from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message - - evm.return_data = b"" - - if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas - push(evm.stack, U256(0)) - return - - call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size - ) - code = get_account(evm.message.block_env.state, code_address).code - - if is_delegated_code and len(code) == 0: - evm.gas_left += gas - push(evm.stack, U256(1)) - return - - child_message = Message( - block_env=evm.message.block_env, - tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, - data=call_data, - code=code, - current_target=to, - depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, - accessed_addresses=evm.accessed_addresses.copy(), - accessed_storage_keys=evm.accessed_storage_keys.copy(), - parent_evm=evm, - ) - child_evm = process_message(child_message) - - if child_evm.error: - incorporate_child_on_error(evm, child_evm) - evm.return_data = child_evm.output - push(evm.stack, U256(0)) - else: - incorporate_child_on_success(evm, child_evm) - evm.return_data = child_evm.output - push(evm.stack, U256(1)) - - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) - memory_write( - evm.memory, - memory_output_start_position, - child_evm.output[:actual_output_size], - ) - - -def call(evm: Evm) -> None: - """ - Message-call into an account. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - gas = Uint(pop(evm.stack)) - to = to_address(pop(evm.stack)) - value = pop(evm.stack) - memory_input_start_position = pop(evm.stack) - memory_input_size = pop(evm.stack) - memory_output_start_position = pop(evm.stack) - memory_output_size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, - [ - (memory_input_start_position, memory_input_size), - (memory_output_start_position, memory_output_size), - ], - ) - - if to in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(to) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - code_address = to - ( - is_delegated_code, - code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost - - create_gas_cost = ( - Uint(0) - if is_account_alive(evm.message.block_env.state, to) or value == 0 - else GAS_NEW_ACCOUNT - ) - transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE - message_call_gas = calculate_message_call_gas( - value, - gas, - Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, - ) - charge_gas(evm, message_call_gas.cost + extend_memory.cost) - 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 - if sender_balance < value: - push(evm.stack, U256(0)) - evm.return_data = b"" - evm.gas_left += message_call_gas.stipend - else: - generic_call( - evm, - message_call_gas.stipend, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - is_delegated_code, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def callcode(evm: Evm) -> None: - """ - Message-call into this account with alternative account’s code. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - gas = Uint(pop(evm.stack)) - code_address = to_address(pop(evm.stack)) - value = pop(evm.stack) - memory_input_start_position = pop(evm.stack) - memory_input_size = pop(evm.stack) - memory_output_start_position = pop(evm.stack) - memory_output_size = pop(evm.stack) - - # GAS - to = evm.message.current_target - - extend_memory = calculate_gas_extend_memory( - evm.memory, - [ - (memory_input_start_position, memory_input_size), - (memory_output_start_position, memory_output_size), - ], - ) - - if code_address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - ( - is_delegated_code, - code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost - - transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE - message_call_gas = calculate_message_call_gas( - value, - gas, - Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + transfer_gas_cost, - ) - charge_gas(evm, message_call_gas.cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.block_env.state, evm.message.current_target - ).balance - if sender_balance < value: - push(evm.stack, U256(0)) - evm.return_data = b"" - evm.gas_left += message_call_gas.stipend - else: - generic_call( - evm, - message_call_gas.stipend, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - is_delegated_code, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def selfdestruct(evm: Evm) -> None: - """ - Halt execution and register account for later deletion. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - beneficiary = to_address(pop(evm.stack)) - - # GAS - gas_cost = GAS_SELF_DESTRUCT - if beneficiary not in evm.accessed_addresses: - 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 - ): - gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT - - charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext - - originator = evm.message.current_target - originator_balance = get_account( - evm.message.block_env.state, originator - ).balance - - move_ether( - evm.message.block_env.state, - originator, - beneficiary, - originator_balance, - ) - - # register account for deletion only if it was created - # in the same transaction - if originator in evm.message.block_env.state.created_accounts: - # If beneficiary is the same as originator, then - # the ether is burnt. - set_account_balance(evm.message.block_env.state, originator, U256(0)) - evm.accounts_to_delete.add(originator) - - # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.block_env.state, beneficiary): - evm.touched_accounts.add(beneficiary) - - # HALT the execution - evm.running = False - - # PROGRAM COUNTER - pass - - -def delegatecall(evm: Evm) -> None: - """ - Message-call into an account. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - gas = Uint(pop(evm.stack)) - code_address = to_address(pop(evm.stack)) - memory_input_start_position = pop(evm.stack) - memory_input_size = pop(evm.stack) - memory_output_start_position = pop(evm.stack) - memory_output_size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, - [ - (memory_input_start_position, memory_input_size), - (memory_output_start_position, memory_output_size), - ], - ) - - if code_address in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - ( - is_delegated_code, - code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost - - message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost - ) - charge_gas(evm, message_call_gas.cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - generic_call( - evm, - message_call_gas.stipend, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - is_delegated_code, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def staticcall(evm: Evm) -> None: - """ - Message-call into an account. - - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - gas = Uint(pop(evm.stack)) - to = to_address(pop(evm.stack)) - memory_input_start_position = pop(evm.stack) - memory_input_size = pop(evm.stack) - memory_output_start_position = pop(evm.stack) - memory_output_size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, - [ - (memory_input_start_position, memory_input_size), - (memory_output_start_position, memory_output_size), - ], - ) - - if to in evm.accessed_addresses: - access_gas_cost = GAS_WARM_ACCESS - else: - evm.accessed_addresses.add(to) - access_gas_cost = GAS_COLD_ACCOUNT_ACCESS - - code_address = to - ( - is_delegated_code, - code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost - - message_call_gas = calculate_message_call_gas( - U256(0), - gas, - Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost, - ) - charge_gas(evm, message_call_gas.cost + extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - generic_call( - evm, - message_call_gas.stipend, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - is_delegated_code, - ) - - # PROGRAM COUNTER - evm.pc += Uint(1) - - -def revert(evm: Evm) -> None: - """ - Stop execution and revert state changes, without consuming all provided gas - and also has the ability to return a reason - Parameters - ---------- - evm : - The current EVM frame. - """ - # STACK - memory_start_index = pop(evm.stack) - size = pop(evm.stack) - - # GAS - extend_memory = calculate_gas_extend_memory( - evm.memory, [(memory_start_index, size)] - ) - - charge_gas(evm, extend_memory.cost) - - # OPERATION - evm.memory += b"\x00" * extend_memory.expand_by - output = memory_read_bytes(evm.memory, memory_start_index, size) - evm.output = bytes(output) - raise Revert - - # PROGRAM COUNTER - pass diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py deleted file mode 100644 index 1c4855ea6e..0000000000 --- a/src/ethereum/osaka/vm/interpreter.py +++ /dev/null @@ -1,328 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Interpreter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -A straightforward interpreter that executes EVM code. -""" -from dataclasses import dataclass -from typing import Optional, Set, Tuple - -from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import U256, Uint, ulen - -from ethereum.exceptions import EthereumException -from ethereum.trace import ( - EvmStop, - OpEnd, - OpException, - OpStart, - PrecompileEnd, - PrecompileStart, - TransactionEnd, - evm_trace, -) - -from ..blocks import Log -from ..fork_types import Address -from ..state import ( - account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, - begin_transaction, - commit_transaction, - destroy_storage, - increment_nonce, - mark_account_created, - move_ether, - rollback_transaction, - set_code, - touch_account, -) -from ..vm import Message -from ..vm.eoa_delegation import set_delegation -from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas -from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Evm -from .exceptions import ( - AddressCollision, - ExceptionalHalt, - InvalidContractPrefix, - InvalidOpcode, - OutOfGasError, - Revert, - StackDepthLimitError, -) -from .instructions import Ops, op_implementation -from .runtime import get_valid_jump_destinations - -STACK_DEPTH_LIMIT = Uint(1024) -MAX_CODE_SIZE = 0x6000 - - -@dataclass -class MessageCallOutput: - """ - Output of a particular message call - - Contains the following: - - 1. `gas_left`: remaining gas after execution. - 2. `refund_counter`: gas to refund after execution. - 3. `logs`: list of `Log` generated during execution. - 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `touched_accounts`: Accounts that have been touched. - 6. `error`: The error from the execution if any. - 7. `return_data`: The output of the execution. - """ - - gas_left: Uint - refund_counter: U256 - logs: Tuple[Log, ...] - accounts_to_delete: Set[Address] - touched_accounts: Set[Address] - error: Optional[EthereumException] - return_data: Bytes - - -def process_message_call(message: Message) -> MessageCallOutput: - """ - If `message.current` is empty then it creates a smart contract - else it executes a call from the `message.caller` to the `message.target`. - - Parameters - ---------- - message : - Transaction specific items. - - Returns - ------- - output : `MessageCallOutput` - Output of the message call - """ - 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) - if is_collision: - return MessageCallOutput( - Uint(0), - U256(0), - tuple(), - set(), - set(), - AddressCollision(), - Bytes(b""), - ) - else: - evm = process_create_message(message) - else: - if message.tx_env.authorizations != (): - refund_counter += set_delegation(message) - evm = process_message(message) - if account_exists_and_is_empty( - block_env.state, Address(message.target) - ): - evm.touched_accounts.add(Address(message.target)) - - if evm.error: - logs: Tuple[Log, ...] = () - accounts_to_delete = set() - touched_accounts = set() - else: - logs = evm.logs - accounts_to_delete = evm.accounts_to_delete - touched_accounts = evm.touched_accounts - refund_counter += U256(evm.refund_counter) - - tx_end = TransactionEnd( - int(message.gas) - int(evm.gas_left), evm.output, evm.error - ) - evm_trace(evm, tx_end) - - return MessageCallOutput( - gas_left=evm.gas_left, - refund_counter=refund_counter, - logs=logs, - accounts_to_delete=accounts_to_delete, - touched_accounts=touched_accounts, - error=evm.error, - return_data=evm.output, - ) - - -def process_create_message(message: Message) -> Evm: - """ - Executes a call to create a smart contract. - - Parameters - ---------- - message : - Transaction specific items. - env : - External items required for EVM execution. - - Returns - ------- - evm: :py:class:`~ethereum.osaka.vm.Evm` - Items containing execution specific objects. - """ - state = message.block_env.state - transient_storage = message.tx_env.transient_storage - # take snapshot of state before processing the message - begin_transaction(state, transient_storage) - - # If the address where the account is being created has storage, it is - # destroyed. This can only happen in the following highly unlikely - # circumstances: - # * The address created by a `CREATE` call collides with a subsequent - # `CREATE` or `CREATE2` call. - # * The first `CREATE` happened before Spurious Dragon and left empty - # code. - destroy_storage(state, 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. - mark_account_created(state, message.current_target) - - increment_nonce(state, message.current_target) - evm = process_message(message) - if not evm.error: - contract_code = evm.output - contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT - try: - if len(contract_code) > 0: - if contract_code[0] == 0xEF: - raise InvalidContractPrefix - charge_gas(evm, contract_code_gas) - if len(contract_code) > MAX_CODE_SIZE: - raise OutOfGasError - except ExceptionalHalt as error: - rollback_transaction(state, transient_storage) - evm.gas_left = Uint(0) - evm.output = b"" - evm.error = error - else: - set_code(state, message.current_target, contract_code) - commit_transaction(state, transient_storage) - else: - rollback_transaction(state, transient_storage) - return evm - - -def process_message(message: Message) -> Evm: - """ - Executes a call to create a smart contract. - - Parameters - ---------- - message : - Transaction specific items. - env : - External items required for EVM execution. - - Returns - ------- - evm: :py:class:`~ethereum.osaka.vm.Evm` - Items containing execution specific objects - """ - state = message.block_env.state - transient_storage = message.tx_env.transient_storage - if message.depth > STACK_DEPTH_LIMIT: - raise StackDepthLimitError("Stack depth limit reached") - - # take snapshot of state before processing the message - begin_transaction(state, transient_storage) - - touch_account(state, message.current_target) - - if message.should_transfer_value and message.value != 0: - move_ether( - state, message.caller, message.current_target, message.value - ) - - evm = execute_code(message) - if evm.error: - # revert state to the last saved checkpoint - # since the message call resulted in an error - rollback_transaction(state, transient_storage) - else: - commit_transaction(state, transient_storage) - return evm - - -def execute_code(message: Message) -> Evm: - """ - Executes bytecode present in the `message`. - - Parameters - ---------- - message : - Transaction specific items. - env : - External items required for EVM execution. - - Returns - ------- - evm: `ethereum.vm.EVM` - Items containing execution specific objects - """ - code = message.code - valid_jump_destinations = get_valid_jump_destinations(code) - - evm = Evm( - pc=Uint(0), - stack=[], - memory=bytearray(), - code=code, - gas_left=message.gas, - valid_jump_destinations=valid_jump_destinations, - logs=(), - refund_counter=0, - running=True, - message=message, - output=b"", - accounts_to_delete=set(), - touched_accounts=set(), - return_data=b"", - error=None, - accessed_addresses=message.accessed_addresses, - accessed_storage_keys=message.accessed_storage_keys, - ) - try: - if evm.message.code_address in PRE_COMPILED_CONTRACTS: - evm_trace(evm, PrecompileStart(evm.message.code_address)) - PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) - evm_trace(evm, PrecompileEnd()) - return evm - - while evm.running and evm.pc < ulen(evm.code): - try: - op = Ops(evm.code[evm.pc]) - except ValueError: - raise InvalidOpcode(evm.code[evm.pc]) - - evm_trace(evm, OpStart(op)) - op_implementation[op](evm) - evm_trace(evm, OpEnd()) - - evm_trace(evm, EvmStop(Ops.STOP)) - - except ExceptionalHalt as error: - evm_trace(evm, OpException(error)) - evm.gas_left = Uint(0) - evm.output = b"" - evm.error = error - except Revert as error: - evm_trace(evm, OpException(error)) - evm.error = error - return evm diff --git a/src/ethereum/osaka/vm/memory.py b/src/ethereum/osaka/vm/memory.py deleted file mode 100644 index aa2e7fdd57..0000000000 --- a/src/ethereum/osaka/vm/memory.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Memory -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -EVM memory operations. -""" -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U256, Uint - -from ethereum.utils.byte import right_pad_zero_bytes - - -def memory_write( - memory: bytearray, start_position: U256, value: Bytes -) -> None: - """ - Writes to memory. - - Parameters - ---------- - memory : - Memory contents of the EVM. - start_position : - Starting pointer to the memory. - value : - Data to write to memory. - """ - memory[start_position : int(start_position) + len(value)] = value - - -def memory_read_bytes( - memory: bytearray, start_position: U256, size: U256 -) -> bytearray: - """ - Read bytes from memory. - - Parameters - ---------- - memory : - Memory contents of the EVM. - start_position : - Starting pointer to the memory. - size : - Size of the data that needs to be read from `start_position`. - - Returns - ------- - data_bytes : - Data read from memory. - """ - return memory[start_position : Uint(start_position) + Uint(size)] - - -def buffer_read(buffer: Bytes, start_position: U256, size: U256) -> Bytes: - """ - Read bytes from a buffer. Padding with zeros if necessary. - - Parameters - ---------- - buffer : - Memory contents of the EVM. - start_position : - Starting pointer to the memory. - size : - Size of the data that needs to be read from `start_position`. - - Returns - ------- - data_bytes : - Data read from memory. - """ - return right_pad_zero_bytes( - buffer[start_position : Uint(start_position) + Uint(size)], size - ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/__init__.py deleted file mode 100644 index 8ab92bb0f3..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Precompiled Contract Addresses -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Addresses of precompiled contracts and mappings to their -implementations. -""" - -from ...utils.hexadecimal import hex_to_address - -__all__ = ( - "ECRECOVER_ADDRESS", - "SHA256_ADDRESS", - "RIPEMD160_ADDRESS", - "IDENTITY_ADDRESS", - "MODEXP_ADDRESS", - "ALT_BN128_ADD_ADDRESS", - "ALT_BN128_MUL_ADDRESS", - "ALT_BN128_PAIRING_CHECK_ADDRESS", - "BLAKE2F_ADDRESS", - "POINT_EVALUATION_ADDRESS", - "BLS12_G1_ADD_ADDRESS", - "BLS12_G1_MSM_ADDRESS", - "BLS12_G2_ADD_ADDRESS", - "BLS12_G2_MSM_ADDRESS", - "BLS12_PAIRING_ADDRESS", - "BLS12_MAP_FP_TO_G1_ADDRESS", - "BLS12_MAP_FP2_TO_G2_ADDRESS", -) - -ECRECOVER_ADDRESS = hex_to_address("0x01") -SHA256_ADDRESS = hex_to_address("0x02") -RIPEMD160_ADDRESS = hex_to_address("0x03") -IDENTITY_ADDRESS = hex_to_address("0x04") -MODEXP_ADDRESS = hex_to_address("0x05") -ALT_BN128_ADD_ADDRESS = hex_to_address("0x06") -ALT_BN128_MUL_ADDRESS = hex_to_address("0x07") -ALT_BN128_PAIRING_CHECK_ADDRESS = hex_to_address("0x08") -BLAKE2F_ADDRESS = hex_to_address("0x09") -POINT_EVALUATION_ADDRESS = hex_to_address("0x0a") -BLS12_G1_ADD_ADDRESS = hex_to_address("0x0b") -BLS12_G1_MSM_ADDRESS = hex_to_address("0x0c") -BLS12_G2_ADD_ADDRESS = hex_to_address("0x0d") -BLS12_G2_MSM_ADDRESS = hex_to_address("0x0e") -BLS12_PAIRING_ADDRESS = hex_to_address("0x0f") -BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10") -BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11") diff --git a/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py deleted file mode 100644 index dc75b40ac6..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) ALT_BN128 CONTRACTS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the ALT_BN128 precompiled contracts. -""" -from ethereum_types.numeric import U256, Uint - -from ethereum.crypto.alt_bn128 import ( - ALT_BN128_CURVE_ORDER, - ALT_BN128_PRIME, - BNF, - BNF2, - BNF12, - BNP, - BNP2, - pairing, -) - -from ...vm import Evm -from ...vm.gas import charge_gas -from ...vm.memory import buffer_read -from ..exceptions import OutOfGasError - - -def alt_bn128_add(evm: Evm) -> None: - """ - The ALT_BN128 addition precompiled contract. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - charge_gas(evm, Uint(150)) - - # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - x1_bytes = buffer_read(data, U256(64), U256(32)) - x1_value = int(U256.from_be_bytes(x1_bytes)) - y1_bytes = buffer_read(data, U256(96), U256(32)) - y1_value = int(U256.from_be_bytes(y1_bytes)) - - for i in (x0_value, y0_value, x1_value, y1_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - p1 = BNP(BNF(x1_value), BNF(y1_value)) - except ValueError: - raise OutOfGasError - - p = p0 + p1 - - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() - - -def alt_bn128_mul(evm: Evm) -> None: - """ - The ALT_BN128 multiplication precompiled contract. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - charge_gas(evm, Uint(6000)) - - # OPERATION - x0_bytes = buffer_read(data, U256(0), U256(32)) - x0_value = int(U256.from_be_bytes(x0_bytes)) - y0_bytes = buffer_read(data, U256(32), U256(32)) - y0_value = int(U256.from_be_bytes(y0_bytes)) - n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) - - for i in (x0_value, y0_value): - if i >= ALT_BN128_PRIME: - raise OutOfGasError - - try: - p0 = BNP(BNF(x0_value), BNF(y0_value)) - except ValueError: - raise OutOfGasError - - p = p0.mul_by(n) - - evm.output = p.x.to_be_bytes32() + p.y.to_be_bytes32() - - -def alt_bn128_pairing_check(evm: Evm) -> None: - """ - The ALT_BN128 pairing check precompiled contract. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - charge_gas(evm, Uint(34000 * (len(data) // 192) + 45000)) - - # OPERATION - if len(data) % 192 != 0: - raise OutOfGasError - result = BNF12.from_int(1) - for i in range(len(data) // 192): - values = [] - for j in range(6): - value = int( - U256.from_be_bytes( - data[i * 192 + 32 * j : i * 192 + 32 * (j + 1)] - ) - ) - if value >= ALT_BN128_PRIME: - raise OutOfGasError - values.append(value) - - try: - p = BNP(BNF(values[0]), BNF(values[1])) - q = BNP2( - BNF2((values[3], values[2])), BNF2((values[5], values[4])) - ) - except ValueError: - raise OutOfGasError() - if p.mul_by(ALT_BN128_CURVE_ORDER) != BNP.point_at_infinity(): - raise OutOfGasError - if q.mul_by(ALT_BN128_CURVE_ORDER) != BNP2.point_at_infinity(): - raise OutOfGasError - if p != BNP.point_at_infinity() and q != BNP2.point_at_infinity(): - result = result * pairing(q, p) - - if result == BNF12.from_int(1): - evm.output = U256(1).to_be_bytes32() - else: - evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py b/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py deleted file mode 100644 index 0d86ba6e85..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Blake2 PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the `Blake2` precompiled contract. -""" -from ethereum.crypto.blake2 import Blake2b - -from ...vm import Evm -from ...vm.gas import GAS_BLAKE2_PER_ROUND, charge_gas -from ..exceptions import InvalidParameter - - -def blake2f(evm: Evm) -> None: - """ - Writes the Blake2 hash to output. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - if len(data) != 213: - raise InvalidParameter - - blake2b = Blake2b() - rounds, h, m, t_0, t_1, f = blake2b.get_blake2_parameters(data) - - charge_gas(evm, GAS_BLAKE2_PER_ROUND * rounds) - if f not in [0, 1]: - raise InvalidParameter - - evm.output = blake2b.compress(rounds, h, m, t_0, t_1, f) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py deleted file mode 100644 index 2126a6ab39..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py +++ /dev/null @@ -1,583 +0,0 @@ -""" -BLS12 381 Precompile -^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Precompile for BLS12-381 curve operations. -""" -from typing import Tuple, Union - -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U256, Uint -from py_ecc.bls12_381.bls12_381_curve import ( - FQ, - FQ2, - b, - b2, - curve_order, - is_on_curve, - multiply, -) -from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ -from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 -from py_ecc.typing import Point2D - -from ....vm.memory import buffer_read -from ...exceptions import InvalidParameter - -P = FQ.field_modulus - -G1_K_DISCOUNT = [ - 1000, - 949, - 848, - 797, - 764, - 750, - 738, - 728, - 719, - 712, - 705, - 698, - 692, - 687, - 682, - 677, - 673, - 669, - 665, - 661, - 658, - 654, - 651, - 648, - 645, - 642, - 640, - 637, - 635, - 632, - 630, - 627, - 625, - 623, - 621, - 619, - 617, - 615, - 613, - 611, - 609, - 608, - 606, - 604, - 603, - 601, - 599, - 598, - 596, - 595, - 593, - 592, - 591, - 589, - 588, - 586, - 585, - 584, - 582, - 581, - 580, - 579, - 577, - 576, - 575, - 574, - 573, - 572, - 570, - 569, - 568, - 567, - 566, - 565, - 564, - 563, - 562, - 561, - 560, - 559, - 558, - 557, - 556, - 555, - 554, - 553, - 552, - 551, - 550, - 549, - 548, - 547, - 547, - 546, - 545, - 544, - 543, - 542, - 541, - 540, - 540, - 539, - 538, - 537, - 536, - 536, - 535, - 534, - 533, - 532, - 532, - 531, - 530, - 529, - 528, - 528, - 527, - 526, - 525, - 525, - 524, - 523, - 522, - 522, - 521, - 520, - 520, - 519, -] - -G2_K_DISCOUNT = [ - 1000, - 1000, - 923, - 884, - 855, - 832, - 812, - 796, - 782, - 770, - 759, - 749, - 740, - 732, - 724, - 717, - 711, - 704, - 699, - 693, - 688, - 683, - 679, - 674, - 670, - 666, - 663, - 659, - 655, - 652, - 649, - 646, - 643, - 640, - 637, - 634, - 632, - 629, - 627, - 624, - 622, - 620, - 618, - 615, - 613, - 611, - 609, - 607, - 606, - 604, - 602, - 600, - 598, - 597, - 595, - 593, - 592, - 590, - 589, - 587, - 586, - 584, - 583, - 582, - 580, - 579, - 578, - 576, - 575, - 574, - 573, - 571, - 570, - 569, - 568, - 567, - 566, - 565, - 563, - 562, - 561, - 560, - 559, - 558, - 557, - 556, - 555, - 554, - 553, - 552, - 552, - 551, - 550, - 549, - 548, - 547, - 546, - 545, - 545, - 544, - 543, - 542, - 541, - 541, - 540, - 539, - 538, - 537, - 537, - 536, - 535, - 535, - 534, - 533, - 532, - 532, - 531, - 530, - 530, - 529, - 528, - 528, - 527, - 526, - 526, - 525, - 524, - 524, -] - -G1_MAX_DISCOUNT = 519 -G2_MAX_DISCOUNT = 524 -MULTIPLIER = Uint(1000) - - -def bytes_to_G1(data: Bytes) -> Point2D: - """ - Decode 128 bytes to a G1 point. Does not perform sub-group check. - - Parameters - ---------- - data : - The bytes data to decode. - - Returns - ------- - point : Point2D - The G1 point. - - Raises - ------ - InvalidParameter - Either a field element is invalid or the point is not on the curve. - """ - if len(data) != 128: - raise InvalidParameter("Input should be 128 bytes long") - - x = int.from_bytes(data[:64], "big") - y = int.from_bytes(data[64:], "big") - - if x >= P: - raise InvalidParameter("Invalid field element") - if y >= P: - raise InvalidParameter("Invalid field element") - - if x == 0 and y == 0: - return None - - point = (FQ(x), FQ(y)) - - # Check if the point is on the curve - if not is_on_curve(point, b): - raise InvalidParameter("Point is not on curve") - - return point - - -def G1_to_bytes(point: Point2D) -> Bytes: - """ - Encode a G1 point to 128 bytes. - - Parameters - ---------- - point : - The G1 point to encode. - - Returns - ------- - data : Bytes - The encoded data. - """ - if point is None: - return b"\x00" * 128 - - x, y = point - - x_bytes = int(x).to_bytes(64, "big") - y_bytes = int(y).to_bytes(64, "big") - - return x_bytes + y_bytes - - -def decode_G1_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: - """ - Decode 160 bytes to a G1 point and a scalar. - - Parameters - ---------- - data : - The bytes data to decode. - - Returns - ------- - point : Tuple[Point2D, int] - The G1 point and the scalar. - - Raises - ------ - InvalidParameter - If the sub-group check failed. - """ - if len(data) != 160: - InvalidParameter("Input should be 160 bytes long") - - p = bytes_to_G1(buffer_read(data, U256(0), U256(128))) - if multiply(p, curve_order) is not None: - raise InvalidParameter("Sub-group check failed.") - - m = int.from_bytes(buffer_read(data, U256(128), U256(32)), "big") - - return p, m - - -def bytes_to_FQ( - data: Bytes, optimized: bool = False -) -> Union[FQ, OPTIMIZED_FQ]: - """ - Decode 64 bytes to a FQ element. - - Parameters - ---------- - data : - The bytes data to decode. - optimized : - Whether to use the optimized FQ implementation. - - Returns - ------- - fq : Union[FQ, OPTIMIZED_FQ] - The FQ element. - - Raises - ------ - InvalidParameter - If the field element is invalid. - """ - if len(data) != 64: - raise InvalidParameter("FQ should be 64 bytes long") - - c = int.from_bytes(data[:64], "big") - - if c >= P: - raise InvalidParameter("Invalid field element") - - if optimized: - return OPTIMIZED_FQ(c) - else: - return FQ(c) - - -def bytes_to_FQ2( - data: Bytes, optimized: bool = False -) -> Union[FQ2, OPTIMIZED_FQ2]: - """ - Decode 128 bytes to a FQ2 element. - - Parameters - ---------- - data : - The bytes data to decode. - optimized : - Whether to use the optimized FQ2 implementation. - - Returns - ------- - fq2 : Union[FQ2, OPTIMIZED_FQ2] - The FQ2 element. - - Raises - ------ - InvalidParameter - If the field element is invalid. - """ - if len(data) != 128: - raise InvalidParameter("FQ2 input should be 128 bytes long") - c_0 = int.from_bytes(data[:64], "big") - c_1 = int.from_bytes(data[64:], "big") - - if c_0 >= P: - raise InvalidParameter("Invalid field element") - if c_1 >= P: - raise InvalidParameter("Invalid field element") - - if optimized: - return OPTIMIZED_FQ2((c_0, c_1)) - else: - return FQ2((c_0, c_1)) - - -def bytes_to_G2(data: Bytes) -> Point2D: - """ - Decode 256 bytes to a G2 point. Does not perform sub-group check. - - Parameters - ---------- - data : - The bytes data to decode. - - Returns - ------- - point : Point2D - The G2 point. - - Raises - ------ - InvalidParameter - Either a field element is invalid or the point is not on the curve. - """ - if len(data) != 256: - raise InvalidParameter("G2 should be 256 bytes long") - - x = bytes_to_FQ2(data[:128]) - y = bytes_to_FQ2(data[128:]) - - assert isinstance(x, FQ2) and isinstance(y, FQ2) - if x == FQ2((0, 0)) and y == FQ2((0, 0)): - return None - - point = (x, y) - - # Check if the point is on the curve - if not is_on_curve(point, b2): - raise InvalidParameter("Point is not on curve") - - return point - - -def FQ2_to_bytes(fq2: FQ2) -> Bytes: - """ - Encode a FQ2 point to 128 bytes. - - Parameters - ---------- - fq2 : - The FQ2 point to encode. - - Returns - ------- - data : Bytes - The encoded data. - """ - c_0, c_1 = fq2.coeffs - return int(c_0).to_bytes(64, "big") + int(c_1).to_bytes(64, "big") - - -def G2_to_bytes(point: Point2D) -> Bytes: - """ - Encode a G2 point to 256 bytes. - - Parameters - ---------- - point : - The G2 point to encode. - - Returns - ------- - data : Bytes - The encoded data. - """ - if point is None: - return b"\x00" * 256 - - x, y = point - - return FQ2_to_bytes(x) + FQ2_to_bytes(y) - - -def decode_G2_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: - """ - Decode 288 bytes to a G2 point and a scalar. - - Parameters - ---------- - data : - The bytes data to decode. - - Returns - ------- - point : Tuple[Point2D, int] - The G2 point and the scalar. - - Raises - ------ - InvalidParameter - If the sub-group check failed. - """ - if len(data) != 288: - InvalidParameter("Input should be 288 bytes long") - - p = bytes_to_G2(buffer_read(data, U256(0), U256(256))) - if multiply(p, curve_order) is not None: - raise InvalidParameter("Sub-group check failed.") - - m = int.from_bytes(buffer_read(data, U256(256), U256(32)), "big") - - return p, m diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py deleted file mode 100644 index 541395de40..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) BLS12 381 CONTRACTS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of pre-compiles in G1 (curve over base prime field). -""" -from ethereum_types.numeric import U256, Uint -from py_ecc.bls12_381.bls12_381_curve import add, multiply -from py_ecc.bls.hash_to_curve import clear_cofactor_G1, map_to_curve_G1 -from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ -from py_ecc.optimized_bls12_381.optimized_curve import normalize - -from ....vm import Evm -from ....vm.gas import ( - GAS_BLS_G1_ADD, - GAS_BLS_G1_MAP, - GAS_BLS_G1_MUL, - charge_gas, -) -from ....vm.memory import buffer_read -from ...exceptions import InvalidParameter -from . import ( - G1_K_DISCOUNT, - G1_MAX_DISCOUNT, - MULTIPLIER, - G1_to_bytes, - bytes_to_FQ, - bytes_to_G1, - decode_G1_scalar_pair, -) - -LENGTH_PER_PAIR = 160 - - -def bls12_g1_add(evm: Evm) -> None: - """ - The bls12_381 G1 point addition precompile. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) != 256: - raise InvalidParameter("Invalid Input Length") - - # GAS - charge_gas(evm, Uint(GAS_BLS_G1_ADD)) - - # OPERATION - p1 = bytes_to_G1(buffer_read(data, U256(0), U256(128))) - p2 = bytes_to_G1(buffer_read(data, U256(128), U256(128))) - - result = add(p1, p2) - - evm.output = G1_to_bytes(result) - - -def bls12_g1_msm(evm: Evm) -> None: - """ - The bls12_381 G1 multi-scalar multiplication precompile. - Note: This uses the naive approach to multi-scalar multiplication - which is not suitably optimized for production clients. Clients are - required to implement a more efficient algorithm such as the Pippenger - algorithm. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: - raise InvalidParameter("Invalid Input Length") - - # GAS - k = len(data) // LENGTH_PER_PAIR - if k <= 128: - discount = Uint(G1_K_DISCOUNT[k - 1]) - else: - discount = Uint(G1_MAX_DISCOUNT) - - gas_cost = Uint(k) * GAS_BLS_G1_MUL * discount // MULTIPLIER - charge_gas(evm, gas_cost) - - # OPERATION - for i in range(k): - start_index = i * LENGTH_PER_PAIR - end_index = start_index + LENGTH_PER_PAIR - - p, m = decode_G1_scalar_pair(data[start_index:end_index]) - product = multiply(p, m) - - if i == 0: - result = product - else: - result = add(result, product) - - evm.output = G1_to_bytes(result) - - -def bls12_map_fp_to_g1(evm: Evm) -> None: - """ - Precompile to map field element to G1. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) != 64: - raise InvalidParameter("Invalid Input Length") - - # GAS - charge_gas(evm, Uint(GAS_BLS_G1_MAP)) - - # OPERATION - field_element = bytes_to_FQ(data, True) - assert isinstance(field_element, OPTIMIZED_FQ) - - g1_uncompressed = clear_cofactor_G1(map_to_curve_G1(field_element)) - g1_normalised = normalize(g1_uncompressed) - - evm.output = G1_to_bytes(g1_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py deleted file mode 100644 index bda7f3641f..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) BLS12 381 G2 CONTRACTS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of pre-compiles in G2 (curve over base prime field). -""" -from ethereum_types.numeric import U256, Uint -from py_ecc.bls12_381.bls12_381_curve import add, multiply -from py_ecc.bls.hash_to_curve import clear_cofactor_G2, map_to_curve_G2 -from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 -from py_ecc.optimized_bls12_381.optimized_curve import normalize - -from ....vm import Evm -from ....vm.gas import ( - GAS_BLS_G2_ADD, - GAS_BLS_G2_MAP, - GAS_BLS_G2_MUL, - charge_gas, -) -from ....vm.memory import buffer_read -from ...exceptions import InvalidParameter -from . import ( - G2_K_DISCOUNT, - G2_MAX_DISCOUNT, - MULTIPLIER, - G2_to_bytes, - bytes_to_FQ2, - bytes_to_G2, - decode_G2_scalar_pair, -) - -LENGTH_PER_PAIR = 288 - - -def bls12_g2_add(evm: Evm) -> None: - """ - The bls12_381 G2 point addition precompile. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) != 512: - raise InvalidParameter("Invalid Input Length") - - # GAS - charge_gas(evm, Uint(GAS_BLS_G2_ADD)) - - # OPERATION - p1 = bytes_to_G2(buffer_read(data, U256(0), U256(256))) - p2 = bytes_to_G2(buffer_read(data, U256(256), U256(256))) - - result = add(p1, p2) - - evm.output = G2_to_bytes(result) - - -def bls12_g2_msm(evm: Evm) -> None: - """ - The bls12_381 G2 multi-scalar multiplication precompile. - Note: This uses the naive approach to multi-scalar multiplication - which is not suitably optimized for production clients. Clients are - required to implement a more efficient algorithm such as the Pippenger - algorithm. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: - raise InvalidParameter("Invalid Input Length") - - # GAS - k = len(data) // LENGTH_PER_PAIR - if k <= 128: - discount = Uint(G2_K_DISCOUNT[k - 1]) - else: - discount = Uint(G2_MAX_DISCOUNT) - - gas_cost = Uint(k) * GAS_BLS_G2_MUL * discount // MULTIPLIER - charge_gas(evm, gas_cost) - - # OPERATION - for i in range(k): - start_index = i * LENGTH_PER_PAIR - end_index = start_index + LENGTH_PER_PAIR - - p, m = decode_G2_scalar_pair(data[start_index:end_index]) - product = multiply(p, m) - - if i == 0: - result = product - else: - result = add(result, product) - - evm.output = G2_to_bytes(result) - - -def bls12_map_fp2_to_g2(evm: Evm) -> None: - """ - Precompile to map field element to G2. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid. - """ - data = evm.message.data - if len(data) != 128: - raise InvalidParameter("Invalid Input Length") - - # GAS - charge_gas(evm, Uint(GAS_BLS_G2_MAP)) - - # OPERATION - field_element = bytes_to_FQ2(data, True) - assert isinstance(field_element, OPTIMIZED_FQ2) - - g2_uncompressed = clear_cofactor_G2(map_to_curve_G2(field_element)) - g2_normalised = normalize(g2_uncompressed) - - evm.output = G2_to_bytes(g2_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py deleted file mode 100644 index 2a03a6897a..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) BLS12 381 PAIRING PRE-COMPILE -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the BLS12 381 pairing pre-compile. -""" -from ethereum_types.numeric import U256, Uint -from py_ecc.bls12_381.bls12_381_curve import FQ12, curve_order, multiply -from py_ecc.bls12_381.bls12_381_pairing import pairing - -from ....vm import Evm -from ....vm.gas import charge_gas -from ....vm.memory import buffer_read -from ...exceptions import InvalidParameter -from . import bytes_to_G1, bytes_to_G2 - - -def bls12_pairing(evm: Evm) -> None: - """ - The bls12_381 pairing precompile. - - Parameters - ---------- - evm : - The current EVM frame. - - Raises - ------ - InvalidParameter - If the input length is invalid or if sub-group check fails. - """ - data = evm.message.data - if len(data) == 0 or len(data) % 384 != 0: - raise InvalidParameter("Invalid Input Length") - - # GAS - k = len(data) // 384 - gas_cost = Uint(32600 * k + 37700) - charge_gas(evm, gas_cost) - - # OPERATION - result = FQ12.one() - for i in range(k): - g1_start = Uint(384 * i) - g2_start = Uint(384 * i + 128) - - g1_point = bytes_to_G1(buffer_read(data, U256(g1_start), U256(128))) - if multiply(g1_point, curve_order) is not None: - raise InvalidParameter("Sub-group check failed.") - - g2_point = bytes_to_G2(buffer_read(data, U256(g2_start), U256(256))) - if multiply(g2_point, curve_order) is not None: - raise InvalidParameter("Sub-group check failed.") - - result *= pairing(g2_point, g1_point) - - if result == FQ12.one(): - evm.output = b"\x00" * 31 + b"\x01" - else: - evm.output = b"\x00" * 32 diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py b/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py deleted file mode 100644 index 1f047d3a44..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) ECRECOVER PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the ECRECOVER precompiled contract. -""" -from ethereum_types.numeric import U256 - -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import InvalidSignatureError -from ethereum.utils.byte import left_pad_zero_bytes - -from ...vm import Evm -from ...vm.gas import GAS_ECRECOVER, charge_gas -from ...vm.memory import buffer_read - - -def ecrecover(evm: Evm) -> None: - """ - Decrypts the address using elliptic curve DSA recovery mechanism and writes - the address to output. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - charge_gas(evm, GAS_ECRECOVER) - - # OPERATION - message_hash_bytes = buffer_read(data, U256(0), U256(32)) - message_hash = Hash32(message_hash_bytes) - v = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) - r = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) - s = U256.from_be_bytes(buffer_read(data, U256(96), U256(32))) - - if v != U256(27) and v != U256(28): - return - if U256(0) >= r or r >= SECP256K1N: - return - if U256(0) >= s or s >= SECP256K1N: - return - - try: - public_key = secp256k1_recover(r, s, v - U256(27), message_hash) - except InvalidSignatureError: - # unable to extract public key - return - - address = keccak256(public_key)[12:32] - padded_address = left_pad_zero_bytes(address, 32) - evm.output = padded_address diff --git a/src/ethereum/osaka/vm/precompiled_contracts/identity.py b/src/ethereum/osaka/vm/precompiled_contracts/identity.py deleted file mode 100644 index 88729c96d7..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/identity.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) IDENTITY PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the `IDENTITY` precompiled contract. -""" -from ethereum_types.numeric import Uint - -from ethereum.utils.numeric import ceil32 - -from ...vm import Evm -from ...vm.gas import GAS_IDENTITY, GAS_IDENTITY_WORD, charge_gas - - -def identity(evm: Evm) -> None: - """ - Writes the message data to output. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - word_count = ceil32(Uint(len(data))) // Uint(32) - charge_gas(evm, GAS_IDENTITY + GAS_IDENTITY_WORD * word_count) - - # OPERATION - evm.output = data diff --git a/src/ethereum/osaka/vm/precompiled_contracts/mapping.py b/src/ethereum/osaka/vm/precompiled_contracts/mapping.py deleted file mode 100644 index 3094d8dff2..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/mapping.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Precompiled Contract Addresses -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Mapping of precompiled contracts their implementations. -""" -from typing import Callable, Dict - -from ...fork_types import Address -from . import ( - ALT_BN128_ADD_ADDRESS, - ALT_BN128_MUL_ADDRESS, - ALT_BN128_PAIRING_CHECK_ADDRESS, - BLAKE2F_ADDRESS, - BLS12_G1_ADD_ADDRESS, - BLS12_G1_MSM_ADDRESS, - BLS12_G2_ADD_ADDRESS, - BLS12_G2_MSM_ADDRESS, - BLS12_MAP_FP2_TO_G2_ADDRESS, - BLS12_MAP_FP_TO_G1_ADDRESS, - BLS12_PAIRING_ADDRESS, - ECRECOVER_ADDRESS, - IDENTITY_ADDRESS, - MODEXP_ADDRESS, - POINT_EVALUATION_ADDRESS, - RIPEMD160_ADDRESS, - SHA256_ADDRESS, -) -from .alt_bn128 import alt_bn128_add, alt_bn128_mul, alt_bn128_pairing_check -from .blake2f import blake2f -from .bls12_381.bls12_381_g1 import ( - bls12_g1_add, - bls12_g1_msm, - bls12_map_fp_to_g1, -) -from .bls12_381.bls12_381_g2 import ( - bls12_g2_add, - bls12_g2_msm, - bls12_map_fp2_to_g2, -) -from .bls12_381.bls12_381_pairing import bls12_pairing -from .ecrecover import ecrecover -from .identity import identity -from .modexp import modexp -from .point_evaluation import point_evaluation -from .ripemd160 import ripemd160 -from .sha256 import sha256 - -PRE_COMPILED_CONTRACTS: Dict[Address, Callable] = { - ECRECOVER_ADDRESS: ecrecover, - SHA256_ADDRESS: sha256, - RIPEMD160_ADDRESS: ripemd160, - IDENTITY_ADDRESS: identity, - MODEXP_ADDRESS: modexp, - ALT_BN128_ADD_ADDRESS: alt_bn128_add, - ALT_BN128_MUL_ADDRESS: alt_bn128_mul, - ALT_BN128_PAIRING_CHECK_ADDRESS: alt_bn128_pairing_check, - BLAKE2F_ADDRESS: blake2f, - POINT_EVALUATION_ADDRESS: point_evaluation, - BLS12_G1_ADD_ADDRESS: bls12_g1_add, - BLS12_G1_MSM_ADDRESS: bls12_g1_msm, - BLS12_G2_ADD_ADDRESS: bls12_g2_add, - BLS12_G2_MSM_ADDRESS: bls12_g2_msm, - BLS12_PAIRING_ADDRESS: bls12_pairing, - BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1, - BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2, -} diff --git a/src/ethereum/osaka/vm/precompiled_contracts/modexp.py b/src/ethereum/osaka/vm/precompiled_contracts/modexp.py deleted file mode 100644 index 403fe86b11..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/modexp.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) MODEXP PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the `MODEXP` precompiled contract. -""" -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U256, Uint - -from ...vm import Evm -from ...vm.gas import charge_gas -from ..memory import buffer_read - -GQUADDIVISOR = Uint(3) - - -def modexp(evm: Evm) -> None: - """ - Calculates `(base**exp) % modulus` for arbitrary sized `base`, `exp` and. - `modulus`. The return value is the same length as the modulus. - """ - data = evm.message.data - - # GAS - base_length = U256.from_be_bytes(buffer_read(data, U256(0), U256(32))) - exp_length = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) - modulus_length = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) - - exp_start = U256(96) + base_length - - exp_head = Uint.from_be_bytes( - buffer_read(data, exp_start, min(U256(32), exp_length)) - ) - - charge_gas( - evm, - gas_cost(base_length, modulus_length, exp_length, exp_head), - ) - - # OPERATION - if base_length == 0 and modulus_length == 0: - evm.output = Bytes() - return - - base = Uint.from_be_bytes(buffer_read(data, U256(96), base_length)) - exp = Uint.from_be_bytes(buffer_read(data, exp_start, exp_length)) - - modulus_start = exp_start + exp_length - modulus = Uint.from_be_bytes( - buffer_read(data, modulus_start, modulus_length) - ) - - if modulus == 0: - evm.output = Bytes(b"\x00") * modulus_length - else: - evm.output = pow(base, exp, modulus).to_bytes( - Uint(modulus_length), "big" - ) - - -def complexity(base_length: U256, modulus_length: U256) -> Uint: - """ - Estimate the complexity of performing a modular exponentiation. - - Parameters - ---------- - - base_length : - Length of the array representing the base integer. - - modulus_length : - Length of the array representing the modulus integer. - - Returns - ------- - - complexity : `Uint` - Complexity of performing the operation. - """ - max_length = max(Uint(base_length), Uint(modulus_length)) - words = (max_length + Uint(7)) // Uint(8) - return words ** Uint(2) - - -def iterations(exponent_length: U256, exponent_head: Uint) -> Uint: - """ - Calculate the number of iterations required to perform a modular - exponentiation. - - Parameters - ---------- - - exponent_length : - Length of the array representing the exponent integer. - - exponent_head : - First 32 bytes of the exponent (with leading zero padding if it is - shorter than 32 bytes), as an unsigned integer. - - Returns - ------- - - iterations : `Uint` - Number of iterations. - """ - if exponent_length <= U256(32) and exponent_head == U256(0): - count = Uint(0) - elif exponent_length <= U256(32): - bit_length = Uint(exponent_head.bit_length()) - - if bit_length > Uint(0): - bit_length -= Uint(1) - - count = bit_length - else: - length_part = Uint(8) * (Uint(exponent_length) - Uint(32)) - bits_part = Uint(exponent_head.bit_length()) - - if bits_part > Uint(0): - bits_part -= Uint(1) - - count = length_part + bits_part - - return max(count, Uint(1)) - - -def gas_cost( - base_length: U256, - modulus_length: U256, - exponent_length: U256, - exponent_head: Uint, -) -> Uint: - """ - Calculate the gas cost of performing a modular exponentiation. - - Parameters - ---------- - - base_length : - Length of the array representing the base integer. - - modulus_length : - Length of the array representing the modulus integer. - - exponent_length : - Length of the array representing the exponent integer. - - exponent_head : - First 32 bytes of the exponent (with leading zero padding if it is - shorter than 32 bytes), as an unsigned integer. - - Returns - ------- - - gas_cost : `Uint` - Gas required for performing the operation. - """ - multiplication_complexity = complexity(base_length, modulus_length) - iteration_count = iterations(exponent_length, exponent_head) - cost = multiplication_complexity * iteration_count - cost //= GQUADDIVISOR - return max(Uint(200), cost) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py b/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py deleted file mode 100644 index 188f90f83f..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) POINT EVALUATION PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the POINT EVALUATION precompiled contract. -""" -from ethereum_types.bytes import Bytes, Bytes32, Bytes48 -from ethereum_types.numeric import U256 - -from ethereum.crypto.kzg import ( - KZGCommitment, - kzg_commitment_to_versioned_hash, - verify_kzg_proof, -) - -from ...vm import Evm -from ...vm.exceptions import KZGProofError -from ...vm.gas import GAS_POINT_EVALUATION, charge_gas - -FIELD_ELEMENTS_PER_BLOB = 4096 -BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513 # noqa: E501 -VERSIONED_HASH_VERSION_KZG = b"\x01" - - -def point_evaluation(evm: Evm) -> None: - """ - A pre-compile that verifies a KZG proof which claims that a blob - (represented by a commitment) evaluates to a given value at a given point. - - Parameters - ---------- - evm : - The current EVM frame. - - """ - data = evm.message.data - if len(data) != 192: - raise KZGProofError - - versioned_hash = data[:32] - z = Bytes32(data[32:64]) - y = Bytes32(data[64:96]) - commitment = KZGCommitment(data[96:144]) - proof = Bytes48(data[144:192]) - - # GAS - charge_gas(evm, GAS_POINT_EVALUATION) - if kzg_commitment_to_versioned_hash(commitment) != versioned_hash: - raise KZGProofError - - # Verify KZG proof with z and y in big endian format - try: - kzg_proof_verification = verify_kzg_proof(commitment, z, y, proof) - except Exception as e: - raise KZGProofError from e - - if not kzg_proof_verification: - raise KZGProofError - - # Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded - # 32 byte big endian values - evm.output = Bytes( - U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() - + U256(BLS_MODULUS).to_be_bytes32() - ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py b/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py deleted file mode 100644 index 6af1086a82..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) RIPEMD160 PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the `RIPEMD160` precompiled contract. -""" -import hashlib - -from ethereum_types.numeric import Uint - -from ethereum.utils.byte import left_pad_zero_bytes -from ethereum.utils.numeric import ceil32 - -from ...vm import Evm -from ...vm.gas import GAS_RIPEMD160, GAS_RIPEMD160_WORD, charge_gas - - -def ripemd160(evm: Evm) -> None: - """ - Writes the ripemd160 hash to output. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - word_count = ceil32(Uint(len(data))) // Uint(32) - charge_gas(evm, GAS_RIPEMD160 + GAS_RIPEMD160_WORD * word_count) - - # OPERATION - hash_bytes = hashlib.new("ripemd160", data).digest() - padded_hash = left_pad_zero_bytes(hash_bytes, 32) - evm.output = padded_hash diff --git a/src/ethereum/osaka/vm/precompiled_contracts/sha256.py b/src/ethereum/osaka/vm/precompiled_contracts/sha256.py deleted file mode 100644 index db33a37967..0000000000 --- a/src/ethereum/osaka/vm/precompiled_contracts/sha256.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) SHA256 PRECOMPILED CONTRACT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the `SHA256` precompiled contract. -""" -import hashlib - -from ethereum_types.numeric import Uint - -from ethereum.utils.numeric import ceil32 - -from ...vm import Evm -from ...vm.gas import GAS_SHA256, GAS_SHA256_WORD, charge_gas - - -def sha256(evm: Evm) -> None: - """ - Writes the sha256 hash to output. - - Parameters - ---------- - evm : - The current EVM frame. - """ - data = evm.message.data - - # GAS - word_count = ceil32(Uint(len(data))) // Uint(32) - charge_gas(evm, GAS_SHA256 + GAS_SHA256_WORD * word_count) - - # OPERATION - evm.output = hashlib.sha256(data).digest() diff --git a/src/ethereum/osaka/vm/runtime.py b/src/ethereum/osaka/vm/runtime.py deleted file mode 100644 index d6bf90a812..0000000000 --- a/src/ethereum/osaka/vm/runtime.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Runtime Operations -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Runtime related operations used while executing EVM code. -""" -from typing import Set - -from ethereum_types.numeric import Uint, ulen - -from .instructions import Ops - - -def get_valid_jump_destinations(code: bytes) -> Set[Uint]: - """ - Analyze the evm code to obtain the set of valid jump destinations. - - Valid jump destinations are defined as follows: - * The jump destination is less than the length of the code. - * The jump destination should have the `JUMPDEST` opcode (0x5B). - * The jump destination shouldn't be part of the data corresponding to - `PUSH-N` opcodes. - - Note - Jump destinations are 0-indexed. - - Parameters - ---------- - code : - The EVM code which is to be executed. - - Returns - ------- - valid_jump_destinations: `Set[Uint]` - The set of valid jump destinations in the code. - """ - valid_jump_destinations = set() - pc = Uint(0) - - while pc < ulen(code): - try: - current_opcode = Ops(code[pc]) - except ValueError: - # Skip invalid opcodes, as they don't affect the jumpdest - # analysis. Nevertheless, such invalid opcodes would be caught - # and raised when the interpreter runs. - pc += Uint(1) - continue - - if current_opcode == Ops.JUMPDEST: - valid_jump_destinations.add(pc) - elif Ops.PUSH1.value <= current_opcode.value <= Ops.PUSH32.value: - # If PUSH-N opcodes are encountered, skip the current opcode along - # with the trailing data segment corresponding to the PUSH-N - # opcodes. - push_data_size = current_opcode.value - Ops.PUSH1.value + 1 - pc += Uint(push_data_size) - - pc += Uint(1) - - return valid_jump_destinations diff --git a/src/ethereum/osaka/vm/stack.py b/src/ethereum/osaka/vm/stack.py deleted file mode 100644 index f28a5b3b88..0000000000 --- a/src/ethereum/osaka/vm/stack.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Ethereum Virtual Machine (EVM) Stack -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Implementation of the stack operators for the EVM. -""" - -from typing import List - -from ethereum_types.numeric import U256 - -from .exceptions import StackOverflowError, StackUnderflowError - - -def pop(stack: List[U256]) -> U256: - """ - Pops the top item off of `stack`. - - Parameters - ---------- - stack : - EVM stack. - - Returns - ------- - value : `U256` - The top element on the stack. - - """ - if len(stack) == 0: - raise StackUnderflowError - - return stack.pop() - - -def push(stack: List[U256], value: U256) -> None: - """ - Pushes `value` onto `stack`. - - Parameters - ---------- - stack : - EVM stack. - - value : - Item to be pushed onto `stack`. - - """ - if len(stack) == 1024: - raise StackOverflowError - - return stack.append(value) diff --git a/tests/osaka/__init__.py b/tests/osaka/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/osaka/test_evm_tools.py b/tests/osaka/test_evm_tools.py deleted file mode 100644 index 846a09fd1e..0000000000 --- a/tests/osaka/test_evm_tools.py +++ /dev/null @@ -1,66 +0,0 @@ -from functools import partial -from typing import Dict - -import pytest - -from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH -from tests.helpers.load_evm_tools_tests import ( - fetch_evm_tools_tests, - idfn, - load_evm_tools_test, -) - -ETHEREUM_STATE_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" -EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" -FORK_NAME = "Osaka" - - -SLOW_TESTS = ( - "CALLBlake2f_MaxRounds", - "CALLCODEBlake2f", - "CALLBlake2f", - "loopExp", - "loopMul", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_non-degeneracy-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_bilinearity-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(G1,-G2)=e(-G1,G2)-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(abG1,G2)-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(G1,abG2)-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-inf_pair-]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-multi_inf_pair-]", -) - - -# Define tests -fetch_tests = partial( - fetch_evm_tools_tests, - fork_name=FORK_NAME, - slow_tests=SLOW_TESTS, -) - -run_tests = partial( - load_evm_tools_test, - fork_name=FORK_NAME, -) - - -# Run tests from ethereum/tests -@pytest.mark.evm_tools -@pytest.mark.parametrize( - "test_case", - fetch_tests(ETHEREUM_STATE_TESTS_DIR), - ids=idfn, -) -def test_ethereum_tests_evm_tools(test_case: Dict) -> None: - run_tests(test_case) - - -# Run EEST test fixtures -@pytest.mark.evm_tools -@pytest.mark.parametrize( - "test_case", - fetch_tests(EEST_STATE_TESTS_DIR), - ids=idfn, -) -def test_eest_evm_tools(test_case: Dict) -> None: - run_tests(test_case) diff --git a/tests/osaka/test_rlp.py b/tests/osaka/test_rlp.py deleted file mode 100644 index dd8247a6e2..0000000000 --- a/tests/osaka/test_rlp.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.crypto.hash import keccak256 -from ethereum.osaka.blocks import Block, Header, Log, Receipt, Withdrawal -from ethereum.osaka.transactions import ( - AccessListTransaction, - FeeMarketTransaction, - LegacyTransaction, - Transaction, - decode_transaction, - encode_transaction, -) -from ethereum.osaka.utils.hexadecimal import hex_to_address -from ethereum.utils.hexadecimal import hex_to_bytes256 - -hash1 = keccak256(b"foo") -hash2 = keccak256(b"bar") -hash3 = keccak256(b"baz") -hash4 = keccak256(b"foobar") -hash5 = keccak256(b"quux") -hash6 = keccak256(b"foobarbaz") -hash7 = keccak256(b"quuxbaz") - -address1 = hex_to_address("0x00000000219ab540356cbb839cbe05303d7705fa") -address2 = hex_to_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") -address3 = hex_to_address("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8") - -bloom = hex_to_bytes256( - "0x886480c00200620d84180d0470000c503081160044d05015808" - "0037401107060120040105281100100104500414203040a208003" - "4814200610da1208a638d16e440c024880800301e1004c2b02285" - "0602000084c3249a0c084569c90c2002001586241041e8004035a" - "4400a0100938001e041180083180b0340661372060401428c0200" - "87410402b9484028100049481900c08034864314688d001548c30" - "00828e542284180280006402a28a0264da00ac223004006209609" - "83206603200084040122a4739080501251542082020a4087c0002" - "81c08800898d0900024047380000127038098e090801080000429" - "0c84201661040200201c0004b8490ad588804" -) - -legacy_transaction = LegacyTransaction( - U256(1), - Uint(2), - Uint(3), - Bytes0(), - U256(4), - Bytes(b"foo"), - U256(27), - U256(5), - U256(6), -) - -access_list_transaction = AccessListTransaction( - U64(1), - U256(1), - Uint(2), - Uint(3), - Bytes0(), - U256(4), - Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), - U256(27), - U256(5), - U256(6), -) - -transaction_1559 = FeeMarketTransaction( - U64(1), - U256(1), - Uint(7), - Uint(2), - Uint(3), - Bytes0(), - U256(4), - Bytes(b"bar"), - ((address1, (hash1, hash2)), (address2, tuple())), - U256(27), - U256(5), - U256(6), -) - -withdrawal = Withdrawal(U64(0), U64(1), address1, U256(2)) - - -header = Header( - parent_hash=hash1, - ommers_hash=hash2, - coinbase=address1, - state_root=hash3, - transactions_root=hash4, - receipt_root=hash5, - bloom=bloom, - difficulty=Uint(1), - number=Uint(2), - gas_limit=Uint(3), - gas_used=Uint(4), - timestamp=U256(5), - extra_data=Bytes(b"foobar"), - prev_randao=Bytes32(b"1234567890abcdef1234567890abcdef"), - nonce=Bytes8(b"12345678"), - base_fee_per_gas=Uint(6), - withdrawals_root=hash6, - parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), - blob_gas_used=U64(7), - excess_blob_gas=U64(8), - requests_hash=hash7, -) - -block = Block( - header=header, - transactions=( - encode_transaction(legacy_transaction), - encode_transaction(access_list_transaction), - encode_transaction(transaction_1559), - ), - ommers=(), - withdrawals=(withdrawal,), -) - -log1 = Log( - address=address1, - topics=(hash1, hash2), - data=Bytes(b"foobar"), -) - -log2 = Log( - address=address1, - topics=(hash1,), - data=Bytes(b"quux"), -) - -receipt = Receipt( - succeeded=True, - cumulative_gas_used=Uint(1), - bloom=bloom, - logs=(log1, log2), -) - - -@pytest.mark.parametrize( - "rlp_object", - [ - legacy_transaction, - access_list_transaction, - transaction_1559, - header, - block, - log1, - log2, - receipt, - withdrawal, - ], -) -def test_cancun_rlp(rlp_object: rlp.Extended) -> None: - encoded = rlp.encode(rlp_object) - assert rlp.decode_to(type(rlp_object), encoded) == rlp_object - - -@pytest.mark.parametrize( - "tx", [legacy_transaction, access_list_transaction, transaction_1559] -) -def test_transaction_encoding(tx: Transaction) -> None: - encoded = encode_transaction(tx) - assert decode_transaction(encoded) == tx diff --git a/tests/osaka/test_state_transition.py b/tests/osaka/test_state_transition.py deleted file mode 100644 index a543ecea3a..0000000000 --- a/tests/osaka/test_state_transition.py +++ /dev/null @@ -1,104 +0,0 @@ -from functools import partial -from typing import Dict - -import pytest - -from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH -from tests.helpers.load_state_tests import ( - Load, - fetch_state_test_files, - idfn, - run_blockchain_st_test, -) - -ETHEREUM_BLOCKCHAIN_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" -EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" -NETWORK = "Osaka" -PACKAGE = "osaka" - -SLOW_TESTS = ( - # GeneralStateTests - "stTimeConsuming/CALLBlake2f_MaxRounds.json", - "stTimeConsuming/static_Call50000_sha256.json", - "vmPerformance/loopExp.json", - "vmPerformance/loopMul.json", - "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Prague", - "CALLBlake2f_d9g0v0_Prague", - "CALLCODEBlake2f_d9g0v0", - # GeneralStateTests - "stRandom/randomStatetest177.json", - "stCreateTest/CreateOOGafterMaxCodesize.json", - # ValidBlockTest - "bcExploitTest/DelegateCallSpam.json", - # InvalidBlockTest - "bcUncleHeaderValidity/nonceWrong.json", - "bcUncleHeaderValidity/wrongMixHash.json", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_non-degeneracy-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_bilinearity-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(G1,-G2\\)=e\\(-G1,G2\\)-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(abG1,G2\\)-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(G1,abG2\\)-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-inf_pair-\\]", - "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-multi_inf_pair-\\]", - "tests/osaka/eip2935_historical_block_hashes_from_state/test_block_hashes\\.py\\:\\:test_block_hashes_history\\[fork_Prague-blockchain_test-full_history_plus_one_check_blockhash_first\\]", -) - -# These are tests that are considered to be incorrect, -# Please provide an explanation when adding entries -IGNORE_TESTS = ( - # ValidBlockTest - "bcForkStressTest/ForkStressTest.json", - "bcGasPricerTest/RPC_API_Test.json", - "bcMultiChainTest", - "bcTotalDifficultyTest", - # InvalidBlockTest - "bcForgedTest", - "bcMultiChainTest", - "GasLimitHigherThan2p63m1_Prague", -) - -# All tests that recursively create a large number of frames (50000) -BIG_MEMORY_TESTS = ( - # GeneralStateTests - "50000_", - "/stQuadraticComplexityTest/", - "/stRandom2/", - "/stRandom/", - "/stSpecialTest/", - "stTimeConsuming/", - "stBadOpcode/", - "stStaticCall/", -) - -# Define Tests -fetch_tests = partial( - fetch_state_test_files, - network=NETWORK, - ignore_list=IGNORE_TESTS, - slow_list=SLOW_TESTS, - big_memory_list=BIG_MEMORY_TESTS, -) - -FIXTURES_LOADER = Load(NETWORK, PACKAGE) - -run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) - - -# Run tests from ethereum/tests -@pytest.mark.parametrize( - "test_case", - fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), - ids=idfn, -) -def test_ethereum_tests(test_case: Dict) -> None: - run_tests(test_case) - - -# Run EEST test fixtures -@pytest.mark.parametrize( - "test_case", - fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), - ids=idfn, -) -def test_eest_tests(test_case: Dict) -> None: - run_tests(test_case) diff --git a/tests/osaka/test_trie.py b/tests/osaka/test_trie.py deleted file mode 100644 index 04c86a1e08..0000000000 --- a/tests/osaka/test_trie.py +++ /dev/null @@ -1,89 +0,0 @@ -import json -from typing import Any - -from ethereum.osaka.fork_types import Bytes -from ethereum.osaka.trie import Trie, root, trie_set -from ethereum.utils.hexadecimal import ( - has_hex_prefix, - hex_to_bytes, - remove_hex_prefix, -) -from tests.helpers import TEST_FIXTURES - -FIXTURE_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] - - -def to_bytes(data: str) -> Bytes: - if data is None: - return b"" - if has_hex_prefix(data): - return hex_to_bytes(data) - - return data.encode() - - -def test_trie_secure_hex() -> None: - tests = load_tests("hex_encoded_securetrie_test.json") - - for name, test in tests.items(): - st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") - for k, v in test.get("in").items(): - trie_set(st, to_bytes(k), to_bytes(v)) - result = root(st) - expected = remove_hex_prefix(test.get("root")) - assert result.hex() == expected, f"test {name} failed" - - -def test_trie_secure() -> None: - tests = load_tests("trietest_secureTrie.json") - - for name, test in tests.items(): - st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") - for t in test.get("in"): - trie_set(st, to_bytes(t[0]), to_bytes(t[1])) - result = root(st) - expected = remove_hex_prefix(test.get("root")) - assert result.hex() == expected, f"test {name} failed" - - -def test_trie_secure_any_order() -> None: - tests = load_tests("trieanyorder_secureTrie.json") - - for name, test in tests.items(): - st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") - for k, v in test.get("in").items(): - trie_set(st, to_bytes(k), to_bytes(v)) - result = root(st) - expected = remove_hex_prefix(test.get("root")) - assert result.hex() == expected, f"test {name} failed" - - -def test_trie() -> None: - tests = load_tests("trietest.json") - - for name, test in tests.items(): - st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") - for t in test.get("in"): - trie_set(st, to_bytes(t[0]), to_bytes(t[1])) - result = root(st) - expected = remove_hex_prefix(test.get("root")) - assert result.hex() == expected, f"test {name} failed" - - -def test_trie_any_order() -> None: - tests = load_tests("trieanyorder.json") - - for name, test in tests.items(): - st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") - for k, v in test.get("in").items(): - trie_set(st, to_bytes(k), to_bytes(v)) - result = root(st) - expected = remove_hex_prefix(test.get("root")) - assert result.hex() == expected, f"test {name} failed" - - -def load_tests(path: str) -> Any: - with open(f"{FIXTURE_PATH}/TrieTests/" + path) as f: - tests = json.load(f) - - return tests From f91833268b915bbffce120d9ee82d3d58860a503 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 8 May 2025 16:09:58 +0200 Subject: [PATCH 64/70] osaka initial commit --- src/ethereum/osaka/__init__.py | 7 + src/ethereum/osaka/blocks.py | 136 +++ src/ethereum/osaka/bloom.py | 86 ++ src/ethereum/osaka/exceptions.py | 24 + src/ethereum/osaka/fork.py | 941 ++++++++++++++++++ src/ethereum/osaka/fork_types.py | 79 ++ src/ethereum/osaka/requests.py | 190 ++++ src/ethereum/osaka/state.py | 690 +++++++++++++ src/ethereum/osaka/transactions.py | 593 +++++++++++ src/ethereum/osaka/trie.py | 494 +++++++++ src/ethereum/osaka/utils/__init__.py | 3 + src/ethereum/osaka/utils/address.py | 93 ++ src/ethereum/osaka/utils/hexadecimal.py | 70 ++ src/ethereum/osaka/utils/message.py | 99 ++ src/ethereum/osaka/vm/__init__.py | 191 ++++ src/ethereum/osaka/vm/eoa_delegation.py | 215 ++++ src/ethereum/osaka/vm/exceptions.py | 140 +++ src/ethereum/osaka/vm/gas.py | 369 +++++++ .../osaka/vm/instructions/__init__.py | 366 +++++++ .../osaka/vm/instructions/arithmetic.py | 374 +++++++ src/ethereum/osaka/vm/instructions/bitwise.py | 240 +++++ src/ethereum/osaka/vm/instructions/block.py | 255 +++++ .../osaka/vm/instructions/comparison.py | 178 ++++ .../osaka/vm/instructions/control_flow.py | 171 ++++ .../osaka/vm/instructions/environment.py | 597 +++++++++++ src/ethereum/osaka/vm/instructions/keccak.py | 64 ++ src/ethereum/osaka/vm/instructions/log.py | 88 ++ src/ethereum/osaka/vm/instructions/memory.py | 177 ++++ src/ethereum/osaka/vm/instructions/stack.py | 209 ++++ src/ethereum/osaka/vm/instructions/storage.py | 184 ++++ src/ethereum/osaka/vm/instructions/system.py | 744 ++++++++++++++ src/ethereum/osaka/vm/interpreter.py | 309 ++++++ src/ethereum/osaka/vm/memory.py | 81 ++ .../vm/precompiled_contracts/__init__.py | 54 + .../vm/precompiled_contracts/alt_bn128.py | 227 +++++ .../osaka/vm/precompiled_contracts/blake2f.py | 41 + .../bls12_381/__init__.py | 583 +++++++++++ .../bls12_381/bls12_381_g1.py | 148 +++ .../bls12_381/bls12_381_g2.py | 148 +++ .../bls12_381/bls12_381_pairing.py | 67 ++ .../vm/precompiled_contracts/ecrecover.py | 63 ++ .../vm/precompiled_contracts/identity.py | 38 + .../osaka/vm/precompiled_contracts/mapping.py | 74 ++ .../osaka/vm/precompiled_contracts/modexp.py | 169 ++++ .../precompiled_contracts/point_evaluation.py | 72 ++ .../vm/precompiled_contracts/ripemd160.py | 43 + .../osaka/vm/precompiled_contracts/sha256.py | 40 + src/ethereum/osaka/vm/runtime.py | 68 ++ src/ethereum/osaka/vm/stack.py | 59 ++ tests/osaka/__init__.py | 0 tests/osaka/test_evm_tools.py | 66 ++ tests/osaka/test_rlp.py | 174 ++++ tests/osaka/test_state_transition.py | 104 ++ tests/osaka/test_trie.py | 89 ++ 54 files changed, 10784 insertions(+) create mode 100644 src/ethereum/osaka/__init__.py create mode 100644 src/ethereum/osaka/blocks.py create mode 100644 src/ethereum/osaka/bloom.py create mode 100644 src/ethereum/osaka/exceptions.py create mode 100644 src/ethereum/osaka/fork.py create mode 100644 src/ethereum/osaka/fork_types.py create mode 100644 src/ethereum/osaka/requests.py create mode 100644 src/ethereum/osaka/state.py create mode 100644 src/ethereum/osaka/transactions.py create mode 100644 src/ethereum/osaka/trie.py create mode 100644 src/ethereum/osaka/utils/__init__.py create mode 100644 src/ethereum/osaka/utils/address.py create mode 100644 src/ethereum/osaka/utils/hexadecimal.py create mode 100644 src/ethereum/osaka/utils/message.py create mode 100644 src/ethereum/osaka/vm/__init__.py create mode 100644 src/ethereum/osaka/vm/eoa_delegation.py create mode 100644 src/ethereum/osaka/vm/exceptions.py create mode 100644 src/ethereum/osaka/vm/gas.py create mode 100644 src/ethereum/osaka/vm/instructions/__init__.py create mode 100644 src/ethereum/osaka/vm/instructions/arithmetic.py create mode 100644 src/ethereum/osaka/vm/instructions/bitwise.py create mode 100644 src/ethereum/osaka/vm/instructions/block.py create mode 100644 src/ethereum/osaka/vm/instructions/comparison.py create mode 100644 src/ethereum/osaka/vm/instructions/control_flow.py create mode 100644 src/ethereum/osaka/vm/instructions/environment.py create mode 100644 src/ethereum/osaka/vm/instructions/keccak.py create mode 100644 src/ethereum/osaka/vm/instructions/log.py create mode 100644 src/ethereum/osaka/vm/instructions/memory.py create mode 100644 src/ethereum/osaka/vm/instructions/stack.py create mode 100644 src/ethereum/osaka/vm/instructions/storage.py create mode 100644 src/ethereum/osaka/vm/instructions/system.py create mode 100644 src/ethereum/osaka/vm/interpreter.py create mode 100644 src/ethereum/osaka/vm/memory.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/__init__.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/blake2f.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/identity.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/mapping.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/modexp.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py create mode 100644 src/ethereum/osaka/vm/precompiled_contracts/sha256.py create mode 100644 src/ethereum/osaka/vm/runtime.py create mode 100644 src/ethereum/osaka/vm/stack.py create mode 100644 tests/osaka/__init__.py create mode 100644 tests/osaka/test_evm_tools.py create mode 100644 tests/osaka/test_rlp.py create mode 100644 tests/osaka/test_state_transition.py create mode 100644 tests/osaka/test_trie.py diff --git a/src/ethereum/osaka/__init__.py b/src/ethereum/osaka/__init__.py new file mode 100644 index 0000000000..803000ba44 --- /dev/null +++ b/src/ethereum/osaka/__init__.py @@ -0,0 +1,7 @@ +""" +The Osaka fork. +""" + +from ethereum.fork_criteria import ByTimestamp + +FORK_CRITERIA = ByTimestamp(1746612311) diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py new file mode 100644 index 0000000000..80b29087e9 --- /dev/null +++ b/src/ethereum/osaka/blocks.py @@ -0,0 +1,136 @@ +""" +A `Block` is a single link in the chain that is Ethereum. Each `Block` contains +a `Header` and zero or more transactions. Each `Header` contains associated +metadata like the block number, parent block hash, and how much gas was +consumed by its transactions. + +Together, these blocks form a cryptographically secure journal recording the +history of all state transitions that have happened since the genesis of the +chain. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes8, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint + +from ..crypto.hash import Hash32 +from .fork_types import Address, Bloom, Root +from .transactions import ( + AccessListTransaction, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + SetCodeTransaction, + Transaction, +) + + +@slotted_freezable +@dataclass +class Withdrawal: + """ + Withdrawals that have been validated on the consensus layer. + """ + + index: U64 + validator_index: U64 + address: Address + amount: U256 + + +@slotted_freezable +@dataclass +class Header: + """ + Header portion of a block on the chain. + """ + + parent_hash: Hash32 + ommers_hash: Hash32 + coinbase: Address + state_root: Root + transactions_root: Root + receipt_root: Root + bloom: Bloom + difficulty: Uint + number: Uint + gas_limit: Uint + gas_used: Uint + timestamp: U256 + extra_data: Bytes + prev_randao: Bytes32 + nonce: Bytes8 + base_fee_per_gas: Uint + withdrawals_root: Root + blob_gas_used: U64 + excess_blob_gas: U64 + parent_beacon_block_root: Root + requests_hash: Hash32 + + +@slotted_freezable +@dataclass +class Block: + """ + A complete block. + """ + + header: Header + transactions: Tuple[Union[Bytes, LegacyTransaction], ...] + ommers: Tuple[Header, ...] + withdrawals: Tuple[Withdrawal, ...] + + +@slotted_freezable +@dataclass +class Log: + """ + Data record produced during the execution of a transaction. + """ + + address: Address + topics: Tuple[Hash32, ...] + data: Bytes + + +@slotted_freezable +@dataclass +class Receipt: + """ + Result of a transaction. + """ + + succeeded: bool + cumulative_gas_used: Uint + bloom: Bloom + logs: Tuple[Log, ...] + + +def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]: + """ + Encodes a receipt. + """ + if isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(receipt) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(receipt) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(receipt) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(receipt) + else: + return receipt + + +def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt: + """ + Decodes a receipt. + """ + if isinstance(receipt, Bytes): + assert receipt[0] in (1, 2, 3, 4) + return rlp.decode_to(Receipt, receipt[1:]) + else: + return receipt diff --git a/src/ethereum/osaka/bloom.py b/src/ethereum/osaka/bloom.py new file mode 100644 index 0000000000..63db26dab5 --- /dev/null +++ b/src/ethereum/osaka/bloom.py @@ -0,0 +1,86 @@ +""" +Ethereum Logs Bloom +^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +This modules defines functions for calculating bloom filters of logs. For the +general theory of bloom filters see e.g. `Wikipedia +`_. Bloom filters are used to allow +for efficient searching of logs by address and/or topic, by rapidly +eliminating blocks and receipts from their search. +""" + +from typing import Tuple + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import Uint + +from ethereum.crypto.hash import keccak256 + +from .blocks import Log +from .fork_types import Bloom + + +def add_to_bloom(bloom: bytearray, bloom_entry: Bytes) -> None: + """ + Add a bloom entry to the bloom filter (`bloom`). + + The number of hash functions used is 3. They are calculated by taking the + least significant 11 bits from the first 3 16-bit words of the + `keccak_256()` hash of `bloom_entry`. + + Parameters + ---------- + bloom : + The bloom filter. + bloom_entry : + An entry which is to be added to bloom filter. + """ + hash = keccak256(bloom_entry) + + for idx in (0, 2, 4): + # Obtain the least significant 11 bits from the pair of bytes + # (16 bits), and set this bit in bloom bytearray. + # The obtained bit is 0-indexed in the bloom filter from the least + # significant bit to the most significant bit. + bit_to_set = Uint.from_be_bytes(hash[idx : idx + 2]) & Uint(0x07FF) + # Below is the index of the bit in the bytearray (where 0-indexed + # byte is the most significant byte) + bit_index = 0x07FF - int(bit_to_set) + + byte_index = bit_index // 8 + bit_value = 1 << (7 - (bit_index % 8)) + bloom[byte_index] = bloom[byte_index] | bit_value + + +def logs_bloom(logs: Tuple[Log, ...]) -> Bloom: + """ + Obtain the logs bloom from a list of log entries. + + The address and each topic of a log are added to the bloom filter. + + Parameters + ---------- + logs : + List of logs for which the logs bloom is to be obtained. + + Returns + ------- + logs_bloom : `Bloom` + The logs bloom obtained which is 256 bytes with some bits set as per + the caller address and the log topics. + """ + bloom: bytearray = bytearray(b"\x00" * 256) + + for log in logs: + add_to_bloom(bloom, log.address) + for topic in log.topics: + add_to_bloom(bloom, topic) + + return Bloom(bloom) diff --git a/src/ethereum/osaka/exceptions.py b/src/ethereum/osaka/exceptions.py new file mode 100644 index 0000000000..5781a2c1c3 --- /dev/null +++ b/src/ethereum/osaka/exceptions.py @@ -0,0 +1,24 @@ +""" +Exceptions specific to this fork. +""" + +from typing import Final + +from ethereum.exceptions import InvalidTransaction + + +class TransactionTypeError(InvalidTransaction): + """ + Unknown [EIP-2718] transaction type byte. + + [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + """ + + transaction_type: Final[int] + """ + The type byte of the transaction that caused the error. + """ + + def __init__(self, transaction_type: int): + super().__init__(f"unknown transaction type `{transaction_type}`") + self.transaction_type = transaction_type diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py new file mode 100644 index 0000000000..7a75825a4c --- /dev/null +++ b/src/ethereum/osaka/fork.py @@ -0,0 +1,941 @@ +""" +Ethereum Specification +^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Entry point for the Ethereum specification. +""" + +from dataclasses import dataclass +from typing import List, Optional, Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import ( + EthereumException, + InvalidBlock, + InvalidSenderError, +) + +from . import vm +from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt +from .bloom import logs_bloom +from .fork_types import Account, Address, Authorization, VersionedHash +from .requests import ( + CONSOLIDATION_REQUEST_TYPE, + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + compute_requests_hash, + parse_deposit_requests, +) +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, + BlobTransaction, + FeeMarketTransaction, + LegacyTransaction, + SetCodeTransaction, + Transaction, + decode_transaction, + encode_transaction, + get_transaction_hash, + recover_sender, + validate_transaction, +) +from .trie import root, trie_set +from .utils.hexadecimal import hex_to_address +from .utils.message import prepare_message +from .vm import Message +from .vm.eoa_delegation import is_valid_delegation +from .vm.gas import ( + calculate_blob_gas_price, + calculate_data_fee, + calculate_excess_blob_gas, + calculate_total_blob_gas, +) +from .vm.interpreter import MessageCallOutput, process_message_call + +BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) +ELASTICITY_MULTIPLIER = Uint(2) +GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) +GAS_LIMIT_MINIMUM = Uint(5000) +EMPTY_OMMER_HASH = keccak256(rlp.encode([])) +SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") +BEACON_ROOTS_ADDRESS = hex_to_address( + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" +) +SYSTEM_TRANSACTION_GAS = Uint(30000000) +MAX_BLOB_GAS_PER_BLOCK = U64(1179648) +VERSIONED_HASH_VERSION_KZG = b"\x01" + +WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x00000961Ef480Eb55e80D19ad83579A64c007002" +) +CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( + "0x0000BBdDc7CE488642fb579F8B00f3a590007251" +) +HISTORY_STORAGE_ADDRESS = hex_to_address( + "0x0000F90827F1C53a10cb7A02335B175320002935" +) +HISTORY_SERVE_WINDOW = 8192 + + +@dataclass +class BlockChain: + """ + History and current state of the block chain. + """ + + blocks: List[Block] + state: State + chain_id: U64 + + +def apply_fork(old: BlockChain) -> BlockChain: + """ + Transforms the state from the previous hard fork (`old`) into the block + chain object for this hard fork and returns it. + + When forks need to implement an irregular state transition, this function + is used to handle the irregularity. See the :ref:`DAO Fork ` for + an example. + + Parameters + ---------- + old : + Previous block chain object. + + Returns + ------- + new : `BlockChain` + Upgraded block chain object for this hard fork. + """ + return old + + +def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: + """ + Obtain the list of hashes of the previous 256 blocks in order of + increasing block number. + + This function will return less hashes for the first 256 blocks. + + The ``BLOCKHASH`` opcode needs to access the latest hashes on the chain, + therefore this function retrieves them. + + Parameters + ---------- + chain : + History and current state. + + Returns + ------- + recent_block_hashes : `List[Hash32]` + Hashes of the recent 256 blocks in order of increasing block number. + """ + recent_blocks = chain.blocks[-255:] + # TODO: This function has not been tested rigorously + if len(recent_blocks) == 0: + return [] + + recent_block_hashes = [] + + for block in recent_blocks: + prev_block_hash = block.header.parent_hash + recent_block_hashes.append(prev_block_hash) + + # We are computing the hash only for the most recent block and not for + # the rest of the blocks as they have successors which have the hash of + # the current block as parent hash. + most_recent_block_hash = keccak256(rlp.encode(recent_blocks[-1].header)) + recent_block_hashes.append(most_recent_block_hash) + + return recent_block_hashes + + +def state_transition(chain: BlockChain, block: Block) -> None: + """ + Attempts to apply a block to an existing block chain. + + All parts of the block's contents need to be verified before being added + to the chain. Blocks are verified by ensuring that the contents of the + block make logical sense with the contents of the parent block. The + information in the block's header must also match the corresponding + information in the block. + + To implement Ethereum, in theory clients are only required to store the + most recent 255 blocks of the chain since as far as execution is + concerned, only those blocks are accessed. Practically, however, clients + should store more blocks to handle reorgs. + + Parameters + ---------- + chain : + History and current state. + block : + Block to apply to `chain`. + """ + validate_header(chain, block.header) + if block.ommers != (): + raise InvalidBlock + + block_env = vm.BlockEnvironment( + chain_id=chain.chain_id, + state=chain.state, + block_gas_limit=block.header.gas_limit, + block_hashes=get_last_256_block_hashes(chain), + coinbase=block.header.coinbase, + number=block.header.number, + base_fee_per_gas=block.header.base_fee_per_gas, + time=block.header.timestamp, + prev_randao=block.header.prev_randao, + excess_blob_gas=block.header.excess_blob_gas, + parent_beacon_block_root=block.header.parent_beacon_block_root, + ) + + block_output = apply_body( + block_env=block_env, + transactions=block.transactions, + withdrawals=block.withdrawals, + ) + block_state_root = state_root(block_env.state) + transactions_root = root(block_output.transactions_trie) + receipt_root = root(block_output.receipts_trie) + block_logs_bloom = logs_bloom(block_output.block_logs) + withdrawals_root = root(block_output.withdrawals_trie) + requests_hash = compute_requests_hash(block_output.requests) + + if block_output.block_gas_used != block.header.gas_used: + raise InvalidBlock( + f"{block_output.block_gas_used} != {block.header.gas_used}" + ) + if transactions_root != block.header.transactions_root: + raise InvalidBlock + if block_state_root != block.header.state_root: + raise InvalidBlock + if receipt_root != block.header.receipt_root: + raise InvalidBlock + if block_logs_bloom != block.header.bloom: + raise InvalidBlock + if withdrawals_root != block.header.withdrawals_root: + raise InvalidBlock + if block_output.blob_gas_used != block.header.blob_gas_used: + raise InvalidBlock + if requests_hash != block.header.requests_hash: + raise InvalidBlock + + chain.blocks.append(block) + if len(chain.blocks) > 255: + # Real clients have to store more blocks to deal with reorgs, but the + # protocol only requires the last 255 + chain.blocks = chain.blocks[-255:] + + +def calculate_base_fee_per_gas( + block_gas_limit: Uint, + parent_gas_limit: Uint, + parent_gas_used: Uint, + parent_base_fee_per_gas: Uint, +) -> Uint: + """ + Calculates the base fee per gas for the block. + + Parameters + ---------- + block_gas_limit : + Gas limit of the block for which the base fee is being calculated. + parent_gas_limit : + Gas limit of the parent block. + parent_gas_used : + Gas used in the parent block. + parent_base_fee_per_gas : + Base fee per gas of the parent block. + + Returns + ------- + base_fee_per_gas : `Uint` + Base fee per gas for the block. + """ + parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER + if not check_gas_limit(block_gas_limit, parent_gas_limit): + raise InvalidBlock + + if parent_gas_used == parent_gas_target: + expected_base_fee_per_gas = parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = max( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR, + Uint(1), + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas + base_fee_per_gas_delta + ) + else: + gas_used_delta = parent_gas_target - parent_gas_used + + parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta + target_fee_gas_delta = parent_fee_gas_delta // parent_gas_target + + base_fee_per_gas_delta = ( + target_fee_gas_delta // BASE_FEE_MAX_CHANGE_DENOMINATOR + ) + + expected_base_fee_per_gas = ( + parent_base_fee_per_gas - base_fee_per_gas_delta + ) + + return Uint(expected_base_fee_per_gas) + + +def validate_header(chain: BlockChain, header: Header) -> None: + """ + Verifies a block header. + + In order to consider a block's header valid, the logic for the + quantities in the header should match the logic for the block itself. + For example the header timestamp should be greater than the block's parent + timestamp because the block was created *after* the parent block. + Additionally, the block's number should be directly following the parent + block's number since it is the next block in the sequence. + + Parameters + ---------- + chain : + History and current state. + header : + Header to check for correctness. + """ + if header.number < Uint(1): + raise InvalidBlock + + parent_header = chain.blocks[-1].header + + excess_blob_gas = calculate_excess_blob_gas(parent_header) + if header.excess_blob_gas != excess_blob_gas: + raise InvalidBlock + + if header.gas_used > header.gas_limit: + raise InvalidBlock + + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_header.base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: + raise InvalidBlock + if header.timestamp <= parent_header.timestamp: + raise InvalidBlock + if header.number != parent_header.number + Uint(1): + raise InvalidBlock + if len(header.extra_data) > 32: + raise InvalidBlock + if header.difficulty != 0: + raise InvalidBlock + if header.nonce != b"\x00\x00\x00\x00\x00\x00\x00\x00": + raise InvalidBlock + if header.ommers_hash != EMPTY_OMMER_HASH: + raise InvalidBlock + + block_parent_hash = keccak256(rlp.encode(parent_header)) + if header.parent_hash != block_parent_hash: + raise InvalidBlock + + +def check_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, +) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: + """ + Check if the transaction is includable in the block. + + Parameters + ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. + tx : + The transaction. + + Returns + ------- + sender_address : + The sender of the transaction. + effective_gas_price : + The price to charge for gas when the transaction is executed. + blob_versioned_hashes : + The blob versioned hashes of the transaction. + tx_blob_gas_used: + The blob gas used by the transaction. + + Raises + ------ + InvalidBlock : + If the transaction is not includable. + """ + gas_available = block_env.block_gas_limit - block_output.block_gas_used + blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used + + if tx.gas > gas_available: + raise InvalidBlock + + tx_blob_gas_used = calculate_total_blob_gas(tx) + if tx_blob_gas_used > blob_gas_available: + raise InvalidBlock + + sender_address = recover_sender(block_env.chain_id, tx) + sender_account = get_account(block_env.state, sender_address) + + if isinstance( + tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) + ): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise InvalidBlock + if tx.max_fee_per_gas < block_env.base_fee_per_gas: + raise InvalidBlock + + priority_fee_per_gas = min( + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas - block_env.base_fee_per_gas, + ) + effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas + max_gas_fee = tx.gas * tx.max_fee_per_gas + else: + if tx.gas_price < block_env.base_fee_per_gas: + raise InvalidBlock + effective_gas_price = tx.gas_price + max_gas_fee = tx.gas * tx.gas_price + + if isinstance(tx, BlobTransaction): + if len(tx.blob_versioned_hashes) == 0: + raise InvalidBlock + for blob_versioned_hash in tx.blob_versioned_hashes: + if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: + raise InvalidBlock + + blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) + if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: + raise InvalidBlock + + max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint( + tx.max_fee_per_blob_gas + ) + blob_versioned_hashes = tx.blob_versioned_hashes + else: + blob_versioned_hashes = () + + if isinstance(tx, (BlobTransaction, SetCodeTransaction)): + if not isinstance(tx.to, Address): + raise InvalidBlock + + if isinstance(tx, SetCodeTransaction): + if not any(tx.authorizations): + raise InvalidBlock + + if sender_account.nonce != tx.nonce: + raise InvalidBlock + if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): + raise InvalidBlock + if sender_account.code and not is_valid_delegation(sender_account.code): + raise InvalidSenderError("not EOA") + + return ( + sender_address, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) + + +def make_receipt( + tx: Transaction, + error: Optional[EthereumException], + cumulative_gas_used: Uint, + logs: Tuple[Log, ...], +) -> Union[Bytes, Receipt]: + """ + Make the receipt for a transaction that was executed. + + Parameters + ---------- + tx : + The executed transaction. + error : + Error in the top level frame of the transaction, if any. + cumulative_gas_used : + The total gas used so far in the block after the transaction was + executed. + logs : + The logs produced by the transaction. + + Returns + ------- + receipt : + The receipt for the transaction. + """ + receipt = Receipt( + succeeded=error is None, + cumulative_gas_used=cumulative_gas_used, + bloom=logs_bloom(logs), + logs=logs, + ) + + return encode_receipt(tx, receipt) + + +def process_system_transaction( + block_env: vm.BlockEnvironment, + target_address: Address, + data: Bytes, +) -> MessageCallOutput: + """ + Process a system transaction. + + Parameters + ---------- + block_env : + The block scoped environment. + target_address : + Address of the contract to call. + data : + Data to pass to the contract. + + Returns + ------- + system_tx_output : `MessageCallOutput` + Output of processing the system transaction. + """ + system_contract_code = get_account(block_env.state, target_address).code + + tx_env = vm.TransactionEnvironment( + origin=SYSTEM_ADDRESS, + gas_price=block_env.base_fee_per_gas, + gas=SYSTEM_TRANSACTION_GAS, + access_list_addresses=set(), + access_list_storage_keys=set(), + transient_storage=TransientStorage(), + blob_versioned_hashes=(), + authorizations=(), + index_in_block=None, + tx_hash=None, + traces=[], + ) + + system_tx_message = Message( + block_env=block_env, + tx_env=tx_env, + caller=SYSTEM_ADDRESS, + target=target_address, + gas=SYSTEM_TRANSACTION_GAS, + value=U256(0), + data=data, + code=system_contract_code, + depth=Uint(0), + current_target=target_address, + code_address=target_address, + should_transfer_value=False, + is_static=False, + accessed_addresses=set(), + accessed_storage_keys=set(), + disable_precompiles=False, + parent_evm=None, + ) + + system_tx_output = process_message_call(system_tx_message) + + return system_tx_output + + +def apply_body( + block_env: vm.BlockEnvironment, + transactions: Tuple[Union[LegacyTransaction, Bytes], ...], + withdrawals: Tuple[Withdrawal, ...], +) -> vm.BlockOutput: + """ + Executes a block. + + Many of the contents of a block are stored in data structures called + tries. There is a transactions trie which is similar to a ledger of the + transactions stored in the current block. There is also a receipts trie + which stores the results of executing a transaction, like the post state + and gas used. This function creates and executes the block that is to be + added to the chain. + + Parameters + ---------- + block_env : + The block scoped environment. + transactions : + Transactions included in the block. + withdrawals : + Withdrawals to be processed in the current block. + + Returns + ------- + block_output : + The block output for the current block. + """ + block_output = vm.BlockOutput() + + process_system_transaction( + block_env=block_env, + target_address=BEACON_ROOTS_ADDRESS, + data=block_env.parent_beacon_block_root, + ) + + process_system_transaction( + block_env=block_env, + target_address=HISTORY_STORAGE_ADDRESS, + data=block_env.block_hashes[-1], # The parent hash + ) + + for i, tx in enumerate(map(decode_transaction, transactions)): + process_transaction(block_env, block_output, tx, Uint(i)) + + process_withdrawals(block_env, block_output, withdrawals) + + process_general_purpose_requests( + block_env=block_env, + block_output=block_output, + ) + + return block_output + + +def process_general_purpose_requests( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, +) -> None: + """ + Process all the requests in the block. + + Parameters + ---------- + block_env : + The execution environment for the Block. + block_output : + The block output for the current block. + """ + # Requests are to be in ascending order of request type + deposit_requests = parse_deposit_requests(block_output) + requests_from_execution = block_output.requests + if len(deposit_requests) > 0: + requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) + + system_withdrawal_tx_output = process_system_transaction( + block_env=block_env, + target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + data=b"", + ) + + if len(system_withdrawal_tx_output.return_data) > 0: + requests_from_execution.append( + WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data + ) + + system_consolidation_tx_output = process_system_transaction( + block_env=block_env, + target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + data=b"", + ) + + if len(system_consolidation_tx_output.return_data) > 0: + requests_from_execution.append( + CONSOLIDATION_REQUEST_TYPE + + system_consolidation_tx_output.return_data + ) + + +def process_transaction( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + tx: Transaction, + index: Uint, +) -> None: + """ + Execute a transaction against the provided environment. + + This function processes the actions needed to execute a transaction. + It decrements the sender's account after calculating the gas fee and + refunds them the proper amount after execution. Calling contracts, + deploying code, and incrementing nonces are all examples of actions that + happen within this function or from a call made within this function. + + Accounts that are marked for deletion are processed and destroyed after + execution. + + Parameters + ---------- + block_env : + Environment for the Ethereum Virtual Machine. + block_output : + The block output for the current block. + tx : + Transaction to execute. + index: + Index of the transaction in the block. + """ + trie_set( + block_output.transactions_trie, + rlp.encode(index), + encode_transaction(tx), + ) + + intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + + ( + sender, + effective_gas_price, + blob_versioned_hashes, + tx_blob_gas_used, + ) = check_transaction( + block_env=block_env, + block_output=block_output, + tx=tx, + ) + + sender_account = get_account(block_env.state, sender) + + if isinstance(tx, BlobTransaction): + blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) + else: + blob_gas_fee = Uint(0) + + effective_gas_fee = tx.gas * effective_gas_price + + gas = tx.gas - intrinsic_gas + increment_nonce(block_env.state, 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) + ) + + access_list_addresses = set() + access_list_storage_keys = set() + access_list_addresses.add(block_env.coinbase) + if isinstance( + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), + ): + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) + + authorizations: Tuple[Authorization, ...] = () + if isinstance(tx, SetCodeTransaction): + authorizations = tx.authorizations + + tx_env = vm.TransactionEnvironment( + origin=sender, + gas_price=effective_gas_price, + gas=gas, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + transient_storage=TransientStorage(), + blob_versioned_hashes=blob_versioned_hashes, + authorizations=authorizations, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), + traces=[], + ) + + message = prepare_message(block_env, tx_env, tx) + + tx_output = process_message_call(message) + + # For EIP-7623 we first calculate the execution_gas_used, which includes + # the execution gas refund. + tx_gas_used_before_refund = tx.gas - tx_output.gas_left + tx_gas_refund = min( + tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter) + ) + tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund + + # Transactions with less execution_gas_used than the floor pay at the + # floor cost. + tx_gas_used_after_refund = max( + tx_gas_used_after_refund, calldata_floor_gas_cost + ) + + tx_gas_left = tx.gas - tx_gas_used_after_refund + gas_refund_amount = tx_gas_left * effective_gas_price + + # For non-1559 transactions effective_gas_price == tx.gas_price + priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas + 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) + + # transfer miner fees + coinbase_balance_after_mining_fee = get_account( + block_env.state, block_env.coinbase + ).balance + U256(transaction_fee) + if coinbase_balance_after_mining_fee != 0: + set_account_balance( + block_env.state, + 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) + + for address in tx_output.accounts_to_delete: + destroy_account(block_env.state, address) + + block_output.block_gas_used += tx_gas_used_after_refund + block_output.blob_gas_used += tx_blob_gas_used + + receipt = make_receipt( + tx, tx_output.error, block_output.block_gas_used, tx_output.logs + ) + + receipt_key = rlp.encode(Uint(index)) + block_output.receipt_keys += (receipt_key,) + + trie_set( + block_output.receipts_trie, + receipt_key, + receipt, + ) + + block_output.block_logs += tx_output.logs + + +def process_withdrawals( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + withdrawals: Tuple[Withdrawal, ...], +) -> None: + """ + Increase the balance of the withdrawing account. + """ + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += wd.amount * U256(10**9) + + for i, wd in enumerate(withdrawals): + trie_set( + block_output.withdrawals_trie, + rlp.encode(Uint(i)), + rlp.encode(wd), + ) + + modify_state(block_env.state, wd.address, increase_recipient_balance) + + if account_exists_and_is_empty(block_env.state, wd.address): + destroy_account(block_env.state, wd.address) + + +def compute_header_hash(header: Header) -> Hash32: + """ + Computes the hash of a block header. + + The header hash of a block is the canonical hash that is used to refer + to a specific block and completely distinguishes a block from another. + + ``keccak256`` is a function that produces a 256 bit hash of any input. + It also takes in any number of bytes as an input and produces a single + hash for them. A hash is a completely unique output for a single input. + So an input corresponds to one unique hash that can be used to identify + the input exactly. + + Prior to using the ``keccak256`` hash function, the header must be + encoded using the Recursive-Length Prefix. See :ref:`rlp`. + RLP encoding the header converts it into a space-efficient format that + allows for easy transfer of data between nodes. The purpose of RLP is to + encode arbitrarily nested arrays of binary data, and RLP is the primary + encoding method used to serialize objects in Ethereum's execution layer. + The only purpose of RLP is to encode structure; encoding specific data + types (e.g. strings, floats) is left up to higher-order protocols. + + Parameters + ---------- + header : + Header of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the header. + """ + return keccak256(rlp.encode(header)) + + +def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool: + """ + Validates the gas limit for a block. + + The bounds of the gas limit, ``max_adjustment_delta``, is set as the + quotient of the parent block's gas limit and the + ``GAS_LIMIT_ADJUSTMENT_FACTOR``. Therefore, if the gas limit that is + passed through as a parameter is greater than or equal to the *sum* of + the parent's gas and the adjustment delta then the limit for gas is too + high and fails this function's check. Similarly, if the limit is less + than or equal to the *difference* of the parent's gas and the adjustment + delta *or* the predefined ``GAS_LIMIT_MINIMUM`` then this function's + check fails because the gas limit doesn't allow for a sufficient or + reasonable amount of gas to be used on a block. + + Parameters + ---------- + gas_limit : + Gas limit to validate. + + parent_gas_limit : + Gas limit of the parent block. + + Returns + ------- + check : `bool` + True if gas limit constraints are satisfied, False otherwise. + """ + max_adjustment_delta = parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR + if gas_limit >= parent_gas_limit + max_adjustment_delta: + return False + if gas_limit <= parent_gas_limit - max_adjustment_delta: + return False + if gas_limit < GAS_LIMIT_MINIMUM: + return False + + return True diff --git a/src/ethereum/osaka/fork_types.py b/src/ethereum/osaka/fork_types.py new file mode 100644 index 0000000000..74cf6c83cb --- /dev/null +++ b/src/ethereum/osaka/fork_types.py @@ -0,0 +1,79 @@ +""" +Ethereum Types +^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Types re-used throughout the specification, which are specific to Ethereum. +""" + +from dataclasses import dataclass + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes20, Bytes256 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U8, U64, U256, Uint + +from ..crypto.hash import Hash32, keccak256 + +Address = Bytes20 +Root = Hash32 +VersionedHash = Hash32 + +Bloom = Bytes256 + + +@slotted_freezable +@dataclass +class Account: + """ + State associated with an address. + """ + + nonce: Uint + balance: U256 + code: Bytes + + +EMPTY_ACCOUNT = Account( + nonce=Uint(0), + balance=U256(0), + code=bytearray(), +) + + +def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: + """ + Encode `Account` dataclass. + + Storage is not stored in the `Account` dataclass, so `Accounts` cannot be + encoded without providing a storage root. + """ + return rlp.encode( + ( + raw_account_data.nonce, + raw_account_data.balance, + storage_root, + keccak256(raw_account_data.code), + ) + ) + + +@slotted_freezable +@dataclass +class Authorization: + """ + The authorization for a set code transaction. + """ + + chain_id: U256 + address: Address + nonce: U64 + y_parity: U8 + r: U256 + s: U256 diff --git a/src/ethereum/osaka/requests.py b/src/ethereum/osaka/requests.py new file mode 100644 index 0000000000..6ed423188f --- /dev/null +++ b/src/ethereum/osaka/requests.py @@ -0,0 +1,190 @@ +""" +Requests were introduced in EIP-7685 as a a general purpose framework for +storing contract-triggered requests. It extends the execution header and +body with a single field each to store the request information. +This inherently exposes the requests to the consensus layer, which can +then process each one. + +[EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 +""" + +from hashlib import sha256 +from typing import List + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import Uint, ulen + +from ethereum.exceptions import InvalidBlock +from ethereum.utils.hexadecimal import hex_to_bytes32 + +from .blocks import decode_receipt +from .trie import trie_get +from .utils.hexadecimal import hex_to_address +from .vm import BlockOutput + +DEPOSIT_CONTRACT_ADDRESS = hex_to_address( + "0x00000000219ab540356cbb839cbe05303d7705fa" +) +DEPOSIT_EVENT_SIGNATURE_HASH = hex_to_bytes32( + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5" +) +DEPOSIT_REQUEST_TYPE = b"\x00" +WITHDRAWAL_REQUEST_TYPE = b"\x01" +CONSOLIDATION_REQUEST_TYPE = b"\x02" + + +DEPOSIT_EVENT_LENGTH = Uint(576) + +PUBKEY_OFFSET = Uint(160) +WITHDRAWAL_CREDENTIALS_OFFSET = Uint(256) +AMOUNT_OFFSET = Uint(320) +SIGNATURE_OFFSET = Uint(384) +INDEX_OFFSET = Uint(512) + +PUBKEY_SIZE = Uint(48) +WITHDRAWAL_CREDENTIALS_SIZE = Uint(32) +AMOUNT_SIZE = Uint(8) +SIGNATURE_SIZE = Uint(96) +INDEX_SIZE = Uint(8) + + +def extract_deposit_data(data: Bytes) -> Bytes: + """ + Extracts Deposit Request from the DepositContract.DepositEvent data. + + Raises + ------ + InvalidBlock : + If the deposit contract did not produce a valid log. + """ + if ulen(data) != DEPOSIT_EVENT_LENGTH: + raise InvalidBlock("Invalid deposit event data length") + + # Check that all the offsets are in order + pubkey_offset = Uint.from_be_bytes(data[0:32]) + if pubkey_offset != PUBKEY_OFFSET: + raise InvalidBlock("Invalid pubkey offset in deposit log") + + withdrawal_credentials_offset = Uint.from_be_bytes(data[32:64]) + if withdrawal_credentials_offset != WITHDRAWAL_CREDENTIALS_OFFSET: + raise InvalidBlock( + "Invalid withdrawal credentials offset in deposit log" + ) + + amount_offset = Uint.from_be_bytes(data[64:96]) + if amount_offset != AMOUNT_OFFSET: + raise InvalidBlock("Invalid amount offset in deposit log") + + signature_offset = Uint.from_be_bytes(data[96:128]) + if signature_offset != SIGNATURE_OFFSET: + raise InvalidBlock("Invalid signature offset in deposit log") + + index_offset = Uint.from_be_bytes(data[128:160]) + if index_offset != INDEX_OFFSET: + raise InvalidBlock("Invalid index offset in deposit log") + + # Check that all the sizes are in order + pubkey_size = Uint.from_be_bytes( + data[pubkey_offset : pubkey_offset + Uint(32)] + ) + if pubkey_size != PUBKEY_SIZE: + raise InvalidBlock("Invalid pubkey size in deposit log") + + pubkey = data[ + pubkey_offset + Uint(32) : pubkey_offset + Uint(32) + PUBKEY_SIZE + ] + + withdrawal_credentials_size = Uint.from_be_bytes( + data[ + withdrawal_credentials_offset : withdrawal_credentials_offset + + Uint(32) + ], + ) + if withdrawal_credentials_size != WITHDRAWAL_CREDENTIALS_SIZE: + raise InvalidBlock( + "Invalid withdrawal credentials size in deposit log" + ) + + withdrawal_credentials = data[ + withdrawal_credentials_offset + + Uint(32) : withdrawal_credentials_offset + + Uint(32) + + WITHDRAWAL_CREDENTIALS_SIZE + ] + + amount_size = Uint.from_be_bytes( + data[amount_offset : amount_offset + Uint(32)] + ) + if amount_size != AMOUNT_SIZE: + raise InvalidBlock("Invalid amount size in deposit log") + + amount = data[ + amount_offset + Uint(32) : amount_offset + Uint(32) + AMOUNT_SIZE + ] + + signature_size = Uint.from_be_bytes( + data[signature_offset : signature_offset + Uint(32)] + ) + if signature_size != SIGNATURE_SIZE: + raise InvalidBlock("Invalid signature size in deposit log") + + signature = data[ + signature_offset + + Uint(32) : signature_offset + + Uint(32) + + SIGNATURE_SIZE + ] + + index_size = Uint.from_be_bytes( + data[index_offset : index_offset + Uint(32)] + ) + if index_size != INDEX_SIZE: + raise InvalidBlock("Invalid index size in deposit log") + + index = data[ + index_offset + Uint(32) : index_offset + Uint(32) + INDEX_SIZE + ] + + return pubkey + withdrawal_credentials + amount + signature + index + + +def parse_deposit_requests(block_output: BlockOutput) -> Bytes: + """ + Parse deposit requests from the block output. + """ + deposit_requests: Bytes = b"" + for key in block_output.receipt_keys: + receipt = trie_get(block_output.receipts_trie, key) + assert receipt is not None + decoded_receipt = decode_receipt(receipt) + for log in decoded_receipt.logs: + if log.address == DEPOSIT_CONTRACT_ADDRESS: + if ( + len(log.topics) > 0 + and log.topics[0] == DEPOSIT_EVENT_SIGNATURE_HASH + ): + request = extract_deposit_data(log.data) + deposit_requests += request + + return deposit_requests + + +def compute_requests_hash(requests: List[Bytes]) -> Bytes: + """ + Get the hash of the requests using the SHA2-256 algorithm. + + Parameters + ---------- + requests : Bytes + The requests to hash. + + Returns + ------- + requests_hash : Bytes + The hash of the requests. + """ + m = sha256() + for request in requests: + m.update(sha256(request).digest()) + + return m.digest() diff --git a/src/ethereum/osaka/state.py b/src/ethereum/osaka/state.py new file mode 100644 index 0000000000..69aaa042d1 --- /dev/null +++ b/src/ethereum/osaka/state.py @@ -0,0 +1,690 @@ +""" +State +^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state contains all information that is preserved between transactions. + +It consists of a main account trie and storage tries for each contract. + +There is a distinction between an account that does not exist and +`EMPTY_ACCOUNT`. +""" + +from dataclasses import dataclass, field +from typing import Callable, Dict, List, Optional, Set, Tuple + +from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.frozen import modify +from ethereum_types.numeric import U256, Uint + +from .fork_types import EMPTY_ACCOUNT, Account, Address, Root +from .trie import EMPTY_TRIE_ROOT, Trie, copy_trie, root, trie_get, trie_set + + +@dataclass +class State: + """ + Contains all information that is preserved between transactions. + """ + + _main_trie: Trie[Address, Optional[Account]] = field( + default_factory=lambda: Trie(secured=True, default=None) + ) + _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( + default_factory=dict + ) + _snapshots: List[ + Tuple[ + Trie[Address, Optional[Account]], + Dict[Address, Trie[Bytes32, U256]], + ] + ] = field(default_factory=list) + created_accounts: Set[Address] = field(default_factory=set) + + +@dataclass +class TransientStorage: + """ + Contains all information that is preserved between message calls + within a transaction. + """ + + _tries: Dict[Address, Trie[Bytes32, U256]] = field(default_factory=dict) + _snapshots: List[Dict[Address, Trie[Bytes32, U256]]] = field( + default_factory=list + ) + + +def close_state(state: State) -> None: + """ + Free resources held by the state. Used by optimized implementations to + release file descriptors. + """ + del state._main_trie + del state._storage_tries + del state._snapshots + del state.created_accounts + + +def begin_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Start a state transaction. + + Transactions are entirely implicit and can be nested. It is not possible to + calculate the state root during a transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.append( + ( + copy_trie(state._main_trie), + {k: copy_trie(t) for (k, t) in state._storage_tries.items()}, + ) + ) + transient_storage._snapshots.append( + {k: copy_trie(t) for (k, t) in transient_storage._tries.items()} + ) + + +def commit_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Commit a state transaction. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._snapshots.pop() + + +def rollback_transaction( + state: State, transient_storage: TransientStorage +) -> None: + """ + Rollback a state transaction, resetting the state to the point when the + corresponding `start_transaction()` call was made. + + Parameters + ---------- + state : State + The state. + transient_storage : TransientStorage + The transient storage of the transaction. + """ + state._main_trie, state._storage_tries = state._snapshots.pop() + if not state._snapshots: + state.created_accounts.clear() + + transient_storage._tries = transient_storage._snapshots.pop() + + +def get_account(state: State, address: Address) -> Account: + """ + Get the `Account` object at an address. Returns `EMPTY_ACCOUNT` if there + is no account at the address. + + Use `get_account_optional()` if you care about the difference between a + non-existent account and `EMPTY_ACCOUNT`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = get_account_optional(state, address) + if isinstance(account, Account): + return account + else: + return EMPTY_ACCOUNT + + +def get_account_optional(state: State, address: Address) -> Optional[Account]: + """ + Get the `Account` object at an address. Returns `None` (rather than + `EMPTY_ACCOUNT`) if there is no account at the address. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to lookup. + + Returns + ------- + account : `Account` + Account at address. + """ + account = trie_get(state._main_trie, address) + return account + + +def set_account( + state: State, address: Address, account: Optional[Account] +) -> None: + """ + Set the `Account` object at an address. Setting to `None` deletes + the account (but not its storage, see `destroy_account()`). + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address to set. + account : `Account` + Account to set at address. + """ + trie_set(state._main_trie, address, account) + + +def destroy_account(state: State, address: Address) -> None: + """ + Completely remove the account at `address` and all of its storage. + + This function is made available exclusively for the `SELFDESTRUCT` + opcode. It is expected that `SELFDESTRUCT` will be disabled in a future + hardfork and this function will be removed. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account to destroy. + """ + destroy_storage(state, address) + set_account(state, address, None) + + +def destroy_storage(state: State, address: Address) -> None: + """ + Completely remove the storage at `address`. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of account whose storage is to be deleted. + """ + if address in state._storage_tries: + del state._storage_tries[address] + + +def mark_account_created(state: State, address: Address) -> None: + """ + Mark an account as having been created in the current transaction. + This information is used by `get_storage_original()` to handle an obscure + edgecase. + + The marker is not removed even if the account creation reverts. Since the + account cannot have had code prior to its creation and can't call + `get_storage_original()`, this is harmless. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account that has been created. + """ + state.created_accounts.add(address) + + +def get_storage(state: State, address: Address, key: Bytes32) -> U256: + """ + Get a value at a storage key on an account. Returns `U256(0)` if the + storage key has not been set previously. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + + Returns + ------- + value : `U256` + Value at the key. + """ + trie = state._storage_tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_storage( + state: State, address: Address, key: Bytes32, value: U256 +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + + Parameters + ---------- + state: `State` + The state + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + assert trie_get(state._main_trie, address) is not None + + trie = state._storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + state._storage_tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del state._storage_tries[address] + + +def storage_root(state: State, address: Address) -> Root: + """ + Calculate the storage root of an account. + + Parameters + ---------- + state: + The state + address : + Address of the account. + + Returns + ------- + root : `Root` + Storage root of the account. + """ + assert not state._snapshots + if address in state._storage_tries: + return root(state._storage_tries[address]) + else: + return EMPTY_TRIE_ROOT + + +def state_root(state: State) -> Root: + """ + Calculate the state root. + + Parameters + ---------- + state: + The current state. + + Returns + ------- + root : `Root` + The state root. + """ + assert not state._snapshots + + def get_storage_root(address: Address) -> Root: + return storage_root(state, address) + + return root(state._main_trie, get_storage_root=get_storage_root) + + +def account_exists(state: State, address: Address) -> bool: + """ + Checks if an account exists in the state trie + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + account_exists : `bool` + True if account exists in the state trie, False otherwise + """ + return get_account_optional(state, address) is not None + + +def account_has_code_or_nonce(state: State, address: Address) -> bool: + """ + Checks if an account has non zero nonce or non empty code + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_code_or_nonce : `bool` + True if the account has non zero nonce or non empty code, + False otherwise. + """ + account = get_account(state, address) + return account.nonce != Uint(0) or account.code != b"" + + +def account_has_storage(state: State, address: Address) -> bool: + """ + Checks if an account has storage. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + has_storage : `bool` + True if the account has storage, False otherwise. + """ + return address in state._storage_tries + + +def is_account_empty(state: State, address: Address) -> bool: + """ + Checks if an account has zero nonce, empty code and zero balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_empty : `bool` + True if if an account has zero nonce, empty code and zero balance, + False otherwise. + """ + account = get_account(state, address) + return ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def account_exists_and_is_empty(state: State, address: Address) -> bool: + """ + Checks if an account exists and has zero nonce, empty code and zero + balance. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + exists_and_is_empty : `bool` + True if an account exists and has zero nonce, empty code and zero + balance, False otherwise. + """ + account = get_account_optional(state, address) + return ( + account is not None + and account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def is_account_alive(state: State, address: Address) -> bool: + """ + Check whether is an account is both in the state and non empty. + + Parameters + ---------- + state: + The state + address: + Address of the account that needs to be checked. + + Returns + ------- + is_alive : `bool` + True if the account is alive. + """ + account = get_account_optional(state, address) + if account is None: + return False + else: + return not ( + account.nonce == Uint(0) + and account.code == b"" + and account.balance == 0 + ) + + +def modify_state( + state: State, address: Address, f: Callable[[Account], None] +) -> None: + """ + Modify an `Account` in the `State`. + """ + set_account(state, address, modify(get_account(state, address), f)) + if account_exists_and_is_empty(state, address): + destroy_account(state, address) + + +def move_ether( + state: State, + sender_address: Address, + recipient_address: Address, + amount: U256, +) -> None: + """ + Move funds between accounts. + """ + + def reduce_sender_balance(sender: Account) -> None: + if sender.balance < amount: + raise AssertionError + sender.balance -= amount + + def increase_recipient_balance(recipient: Account) -> None: + recipient.balance += amount + + modify_state(state, sender_address, reduce_sender_balance) + modify_state(state, recipient_address, increase_recipient_balance) + + +def set_account_balance(state: State, address: Address, amount: U256) -> None: + """ + Sets the balance of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + + amount: + The amount that needs to set in balance. + """ + + def set_balance(account: Account) -> None: + account.balance = amount + + modify_state(state, address, set_balance) + + +def increment_nonce(state: State, address: Address) -> None: + """ + Increments the nonce of an account. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose nonce needs to be incremented. + """ + + def increase_nonce(sender: Account) -> None: + sender.nonce += Uint(1) + + modify_state(state, address, increase_nonce) + + +def set_code(state: State, address: Address, code: Bytes) -> None: + """ + Sets Account code. + + Parameters + ---------- + state: + The current state. + + address: + Address of the account whose code needs to be update. + + code: + The bytecode that needs to be set. + """ + + def write_code(sender: Account) -> None: + sender.code = code + + modify_state(state, address, write_code) + + +def get_storage_original(state: State, address: Address, key: Bytes32) -> U256: + """ + Get the original value in a storage slot i.e. the value before the current + transaction began. This function reads the value from the snapshots taken + before executing the transaction. + + Parameters + ---------- + state: + The current state. + address: + Address of the account to read the value from. + key: + Key of the storage slot. + """ + # In the transaction where an account is created, its preexisting storage + # is ignored. + if address in state.created_accounts: + return U256(0) + + _, original_trie = state._snapshots[0] + original_account_trie = original_trie.get(address) + + if original_account_trie is None: + original_value = U256(0) + else: + original_value = trie_get(original_account_trie, key) + + assert isinstance(original_value, U256) + + return original_value + + +def get_transient_storage( + transient_storage: TransientStorage, address: Address, key: Bytes32 +) -> U256: + """ + Get a value at a storage key on an account from transient storage. + Returns `U256(0)` if the storage key has not been set previously. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to lookup. + Returns + ------- + value : `U256` + Value at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + +def set_transient_storage( + transient_storage: TransientStorage, + address: Address, + key: Bytes32, + value: U256, +) -> None: + """ + Set a value at a storage key on an account. Setting to `U256(0)` deletes + the key. + Parameters + ---------- + transient_storage: `TransientStorage` + The transient storage + address : `Address` + Address of the account. + key : `Bytes` + Key to set. + value : `U256` + Value to set at the key. + """ + trie = transient_storage._tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + transient_storage._tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del transient_storage._tries[address] diff --git a/src/ethereum/osaka/transactions.py b/src/ethereum/osaka/transactions.py new file mode 100644 index 0000000000..d88cdcea00 --- /dev/null +++ b/src/ethereum/osaka/transactions.py @@ -0,0 +1,593 @@ +""" +Transactions are atomic units of work created externally to Ethereum and +submitted to be executed. If Ethereum is viewed as a state machine, +transactions are the events that move between states. +""" +from dataclasses import dataclass +from typing import Tuple, Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U64, U256, Uint, ulen + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError, InvalidTransaction + +from .exceptions import TransactionTypeError +from .fork_types import Address, Authorization, VersionedHash + +TX_BASE_COST = Uint(21000) +FLOOR_CALLDATA_COST = Uint(10) +STANDARD_CALLDATA_TOKEN_COST = Uint(4) +TX_CREATE_COST = Uint(32000) +TX_ACCESS_LIST_ADDRESS_COST = Uint(2400) +TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900) + + +@slotted_freezable +@dataclass +class LegacyTransaction: + """ + Atomic operation performed on the block chain. + """ + + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + v: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class Access: + """ + A mapping from account address to storage slots that are pre-warmed as part + of a transaction. + """ + + account: Address + slots: Tuple[Bytes32, ...] + + +@slotted_freezable +@dataclass +class AccessListTransaction: + """ + The transaction type added in EIP-2930 to support access lists. + """ + + chain_id: U64 + nonce: U256 + gas_price: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Access, ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class FeeMarketTransaction: + """ + The transaction type added in EIP-1559. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Union[Bytes0, Address] + value: U256 + data: Bytes + access_list: Tuple[Access, ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class BlobTransaction: + """ + The transaction type added in EIP-4844. + """ + + chain_id: U64 + nonce: U256 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Access, ...] + max_fee_per_blob_gas: U256 + blob_versioned_hashes: Tuple[VersionedHash, ...] + y_parity: U256 + r: U256 + s: U256 + + +@slotted_freezable +@dataclass +class SetCodeTransaction: + """ + The transaction type added in EIP-7702. + """ + + chain_id: U64 + nonce: U64 + max_priority_fee_per_gas: Uint + max_fee_per_gas: Uint + gas: Uint + to: Address + value: U256 + data: Bytes + access_list: Tuple[Access, ...] + authorizations: Tuple[Authorization, ...] + y_parity: U256 + r: U256 + s: U256 + + +Transaction = Union[ + LegacyTransaction, + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, +] + + +def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]: + """ + Encode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, LegacyTransaction): + return tx + elif isinstance(tx, AccessListTransaction): + return b"\x01" + rlp.encode(tx) + elif isinstance(tx, FeeMarketTransaction): + return b"\x02" + rlp.encode(tx) + elif isinstance(tx, BlobTransaction): + return b"\x03" + rlp.encode(tx) + elif isinstance(tx, SetCodeTransaction): + return b"\x04" + rlp.encode(tx) + else: + raise Exception(f"Unable to encode transaction of type {type(tx)}") + + +def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: + """ + Decode a transaction. Needed because non-legacy transactions aren't RLP. + """ + if isinstance(tx, Bytes): + if tx[0] == 1: + return rlp.decode_to(AccessListTransaction, tx[1:]) + elif tx[0] == 2: + return rlp.decode_to(FeeMarketTransaction, tx[1:]) + elif tx[0] == 3: + return rlp.decode_to(BlobTransaction, tx[1:]) + elif tx[0] == 4: + return rlp.decode_to(SetCodeTransaction, tx[1:]) + else: + raise TransactionTypeError(tx[0]) + else: + return tx + + +def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost charged to the transaction + based on the calldata size. + + Raises + ------ + InvalidBlock : + If the transaction is not valid. + """ + from .vm.interpreter import MAX_CODE_SIZE + + intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) + if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + raise InvalidTransaction("Insufficient gas") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise InvalidTransaction("Nonce too high") + if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: + raise InvalidTransaction("Code size too large") + + return intrinsic_gas, calldata_floor_gas_cost + + +def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + intrinsic_gas : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + calldata_floor_gas_cost : `ethereum.base_types.Uint` + The eip-7623 minimum gas cost used by the transaction + based on the calldata size. + """ + from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST + from .vm.gas import init_code_cost + + zero_bytes = 0 + for byte in tx.data: + if byte == 0: + zero_bytes += 1 + + tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4) + # EIP-7623 floor price (note: no EVM costs) + calldata_floor_gas_cost = ( + tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST + ) + + data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data)) + else: + create_cost = Uint(0) + + access_list_cost = Uint(0) + if isinstance( + tx, + ( + AccessListTransaction, + FeeMarketTransaction, + BlobTransaction, + SetCodeTransaction, + ), + ): + for access in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += ( + ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST + ) + + auth_cost = Uint(0) + if isinstance(tx, SetCodeTransaction): + auth_cost += Uint(PER_EMPTY_ACCOUNT_COST * len(tx.authorizations)) + + return ( + Uint( + TX_BASE_COST + + data_cost + + create_cost + + access_list_cost + + auth_cost + ), + calldata_floor_gas_cost, + ) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + elif isinstance(tx, BlobTransaction): + if tx.y_parity not in (U256(0), U256(1)): + raise InvalidSignatureError("bad y_parity") + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_4844(tx) + ) + elif isinstance(tx, SetCodeTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_7702(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_4844(tx: BlobTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-4844 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x03" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.max_fee_per_blob_gas, + tx.blob_versioned_hashes, + ) + ) + ) + + +def signing_hash_7702(tx: SetCodeTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-7702 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x04" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.authorizations, + ) + ) + ) + + +def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32: + """ + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + assert isinstance(tx, (LegacyTransaction, Bytes)) + if isinstance(tx, LegacyTransaction): + return keccak256(rlp.encode(tx)) + else: + return keccak256(tx) diff --git a/src/ethereum/osaka/trie.py b/src/ethereum/osaka/trie.py new file mode 100644 index 0000000000..0c6cbdba30 --- /dev/null +++ b/src/ethereum/osaka/trie.py @@ -0,0 +1,494 @@ +""" +State Trie +^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The state trie is the structure responsible for storing +`.fork_types.Account` objects. +""" + +import copy +from dataclasses import dataclass, field +from typing import ( + Callable, + Dict, + Generic, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + cast, +) + +from ethereum_rlp import Extended, rlp +from ethereum_types.bytes import Bytes +from ethereum_types.frozen import slotted_freezable +from ethereum_types.numeric import U256, Uint +from typing_extensions import assert_type + +from ethereum.cancun import trie as previous_trie +from ethereum.crypto.hash import keccak256 +from ethereum.utils.hexadecimal import hex_to_bytes + +from .blocks import Receipt, Withdrawal +from .fork_types import Account, Address, Root, encode_account +from .transactions import LegacyTransaction + +# note: an empty trie (regardless of whether it is secured) has root: +# +# keccak256(RLP(b'')) +# == +# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 # noqa: E501,SC10 +# +# also: +# +# keccak256(RLP(())) +# == +# 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 # noqa: E501,SC10 +# +# which is the sha3Uncles hash in block header with no uncles +EMPTY_TRIE_ROOT = Root( + hex_to_bytes( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) +) + +Node = Union[ + Account, Bytes, LegacyTransaction, Receipt, Uint, U256, Withdrawal, None +] +K = TypeVar("K", bound=Bytes) +V = TypeVar( + "V", + Optional[Account], + Optional[Bytes], + Bytes, + Optional[Union[LegacyTransaction, Bytes]], + Optional[Union[Receipt, Bytes]], + Optional[Union[Withdrawal, Bytes]], + Uint, + U256, +) + + +@slotted_freezable +@dataclass +class LeafNode: + """Leaf node in the Merkle Trie""" + + rest_of_key: Bytes + value: Extended + + +@slotted_freezable +@dataclass +class ExtensionNode: + """Extension node in the Merkle Trie""" + + key_segment: Bytes + subnode: Extended + + +BranchSubnodes = Tuple[ + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, + Extended, +] + + +@slotted_freezable +@dataclass +class BranchNode: + """Branch node in the Merkle Trie""" + + subnodes: BranchSubnodes + value: Extended + + +InternalNode = Union[LeafNode, ExtensionNode, BranchNode] + + +def encode_internal_node(node: Optional[InternalNode]) -> Extended: + """ + Encodes a Merkle Trie node into its RLP form. The RLP will then be + serialized into a `Bytes` and hashed unless it is less that 32 bytes + when serialized. + + This function also accepts `None`, representing the absence of a node, + which is encoded to `b""`. + + Parameters + ---------- + node : Optional[InternalNode] + The node to encode. + + Returns + ------- + encoded : `Extended` + The node encoded as RLP. + """ + unencoded: Extended + if node is None: + unencoded = b"" + elif isinstance(node, LeafNode): + unencoded = ( + nibble_list_to_compact(node.rest_of_key, True), + node.value, + ) + elif isinstance(node, ExtensionNode): + unencoded = ( + nibble_list_to_compact(node.key_segment, False), + node.subnode, + ) + elif isinstance(node, BranchNode): + unencoded = list(node.subnodes) + [node.value] + else: + raise AssertionError(f"Invalid internal node type {type(node)}!") + + encoded = rlp.encode(unencoded) + if len(encoded) < 32: + return unencoded + else: + return keccak256(encoded) + + +def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: + """ + Encode a Node for storage in the Merkle Trie. + + Currently mostly an unimplemented stub. + """ + if isinstance(node, Account): + assert storage_root is not None + return encode_account(node, storage_root) + elif isinstance(node, (LegacyTransaction, Receipt, Withdrawal, U256)): + return rlp.encode(node) + elif isinstance(node, Bytes): + return node + else: + return previous_trie.encode_node(node, storage_root) + + +@dataclass +class Trie(Generic[K, V]): + """ + The Merkle Trie. + """ + + secured: bool + default: V + _data: Dict[K, V] = field(default_factory=dict) + + +def copy_trie(trie: Trie[K, V]) -> Trie[K, V]: + """ + Create a copy of `trie`. Since only frozen objects may be stored in tries, + the contents are reused. + + Parameters + ---------- + trie: `Trie` + Trie to copy. + + Returns + ------- + new_trie : `Trie[K, V]` + A copy of the trie. + """ + return Trie(trie.secured, trie.default, copy.copy(trie._data)) + + +def trie_set(trie: Trie[K, V], key: K, value: V) -> None: + """ + Stores an item in a Merkle Trie. + + This method deletes the key if `value == trie.default`, because the Merkle + Trie represents the default value by omitting it from the trie. + + Parameters + ---------- + trie: `Trie` + Trie to store in. + key : `Bytes` + Key to lookup. + value : `V` + Node to insert at `key`. + """ + if value == trie.default: + if key in trie._data: + del trie._data[key] + else: + trie._data[key] = value + + +def trie_get(trie: Trie[K, V], key: K) -> V: + """ + Gets an item from the Merkle Trie. + + This method returns `trie.default` if the key is missing. + + Parameters + ---------- + trie: + Trie to lookup in. + key : + Key to lookup. + + Returns + ------- + node : `V` + Node at `key` in the trie. + """ + return trie._data.get(key, trie.default) + + +def common_prefix_length(a: Sequence, b: Sequence) -> int: + """ + Find the longest common prefix of two sequences. + """ + for i in range(len(a)): + if i >= len(b) or a[i] != b[i]: + return i + return len(a) + + +def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes: + """ + Compresses nibble-list into a standard byte array with a flag. + + A nibble-list is a list of byte values no greater than `15`. The flag is + encoded in high nibble of the highest byte. The flag nibble can be broken + down into two two-bit flags. + + Highest nibble:: + + +---+---+----------+--------+ + | _ | _ | is_leaf | parity | + +---+---+----------+--------+ + 3 2 1 0 + + + The lowest bit of the nibble encodes the parity of the length of the + remaining nibbles -- `0` when even and `1` when odd. The second lowest bit + is used to distinguish leaf and extension nodes. The other two bits are not + used. + + Parameters + ---------- + x : + Array of nibbles. + is_leaf : + True if this is part of a leaf node, or false if it is an extension + node. + + Returns + ------- + compressed : `bytearray` + Compact byte array. + """ + compact = bytearray() + + if len(x) % 2 == 0: # ie even length + compact.append(16 * (2 * is_leaf)) + for i in range(0, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + else: + compact.append(16 * ((2 * is_leaf) + 1) + x[0]) + for i in range(1, len(x), 2): + compact.append(16 * x[i] + x[i + 1]) + + return Bytes(compact) + + +def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: + """ + Converts a `Bytes` into to a sequence of nibbles (bytes with value < 16). + + Parameters + ---------- + bytes_: + The `Bytes` to convert. + + Returns + ------- + nibble_list : `Bytes` + The `Bytes` in nibble-list format. + """ + nibble_list = bytearray(2 * len(bytes_)) + for byte_index, byte in enumerate(bytes_): + nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 + nibble_list[byte_index * 2 + 1] = byte & 0x0F + return Bytes(nibble_list) + + +def _prepare_trie( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Mapping[Bytes, Bytes]: + """ + Prepares the trie for root calculation. Removes values that are empty, + hashes the keys (if `secured == True`) and encodes all the nodes. + + Parameters + ---------- + trie : + The `Trie` to prepare. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + Returns + ------- + out : `Mapping[ethereum.base_types.Bytes, Node]` + Object with keys mapped to nibble-byte form. + """ + mapped: MutableMapping[Bytes, Bytes] = {} + + for preimage, value in trie._data.items(): + if isinstance(value, Account): + assert get_storage_root is not None + address = Address(preimage) + encoded_value = encode_node(value, get_storage_root(address)) + else: + encoded_value = encode_node(value) + if encoded_value == b"": + raise AssertionError + key: Bytes + if trie.secured: + # "secure" tries hash keys once before construction + key = keccak256(preimage) + else: + key = preimage + mapped[bytes_to_nibble_list(key)] = encoded_value + + return mapped + + +def root( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Root: + """ + Computes the root of a modified merkle patricia trie (MPT). + + Parameters + ---------- + trie : + `Trie` to get the root of. + get_storage_root : + Function to get the storage root of an account. Needed to encode + `Account` objects. + + + Returns + ------- + root : `.fork_types.Root` + MPT root of the underlying key-value pairs. + """ + obj = _prepare_trie(trie, get_storage_root) + + root_node = encode_internal_node(patricialize(obj, Uint(0))) + if len(rlp.encode(root_node)) < 32: + return keccak256(rlp.encode(root_node)) + else: + assert isinstance(root_node, Bytes) + return Root(root_node) + + +def patricialize( + obj: Mapping[Bytes, Bytes], level: Uint +) -> Optional[InternalNode]: + """ + Structural composition function. + + Used to recursively patricialize and merkleize a dictionary. Includes + memoization of the tree structure and hashes. + + Parameters + ---------- + obj : + Underlying trie key-value pairs, with keys in nibble-list format. + level : + Current trie level. + + Returns + ------- + node : `ethereum.base_types.Bytes` + Root node of `obj`. + """ + if len(obj) == 0: + return None + + arbitrary_key = next(iter(obj)) + + # if leaf node + if len(obj) == 1: + leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) + return leaf + + # prepare for extension node check by finding max j such that all keys in + # obj have the same key[i:j] + substring = arbitrary_key[level:] + prefix_length = len(substring) + for key in obj: + prefix_length = min( + prefix_length, common_prefix_length(substring, key[level:]) + ) + + # finished searching, found another key at the current level + if prefix_length == 0: + break + + # if extension node + if prefix_length > 0: + prefix = arbitrary_key[int(level) : int(level) + prefix_length] + return ExtensionNode( + prefix, + encode_internal_node( + patricialize(obj, level + Uint(prefix_length)) + ), + ) + + branches: List[MutableMapping[Bytes, Bytes]] = [] + for _ in range(16): + branches.append({}) + value = b"" + for key in obj: + if len(key) == level: + # shouldn't ever have an account or receipt in an internal node + if isinstance(obj[key], (Account, Receipt, Uint)): + raise AssertionError + value = obj[key] + else: + branches[key[level]][key] = obj[key] + + subnodes = tuple( + encode_internal_node(patricialize(branches[k], level + Uint(1))) + for k in range(16) + ) + return BranchNode( + cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), + value, + ) diff --git a/src/ethereum/osaka/utils/__init__.py b/src/ethereum/osaka/utils/__init__.py new file mode 100644 index 0000000000..224a4d269b --- /dev/null +++ b/src/ethereum/osaka/utils/__init__.py @@ -0,0 +1,3 @@ +""" +Utility functions unique to this particular fork. +""" diff --git a/src/ethereum/osaka/utils/address.py b/src/ethereum/osaka/utils/address.py new file mode 100644 index 0000000000..1872dcf317 --- /dev/null +++ b/src/ethereum/osaka/utils/address.py @@ -0,0 +1,93 @@ +""" +Hardfork Utility Functions For Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Address specific functions used in this osaka version of +specification. +""" +from typing import Union + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.byte import left_pad_zero_bytes + +from ..fork_types import Address + + +def to_address(data: Union[Uint, U256]) -> Address: + """ + Convert a Uint or U256 value to a valid address (20 bytes). + + Parameters + ---------- + data : + The string to be converted to bytes. + + Returns + ------- + address : `Address` + The obtained address. + """ + return Address(data.to_be_bytes32()[-20:]) + + +def compute_contract_address(address: Address, nonce: Uint) -> Address: + """ + Computes address of the new account that needs to be created. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + nonce : + The transaction count of the account that wants to create the new + account. + + Returns + ------- + address: `Address` + The computed address of the new account. + """ + computed_address = keccak256(rlp.encode([address, nonce])) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + return Address(padded_address) + + +def compute_create2_contract_address( + address: Address, salt: Bytes32, call_data: bytearray +) -> Address: + """ + Computes address of the new account that needs to be created, which is + based on the sender address, salt and the call data as well. + + Parameters + ---------- + address : + The address of the account that wants to create the new account. + salt : + Address generation salt. + call_data : + The code of the new account which is to be created. + + Returns + ------- + address: `ethereum.osaka.fork_types.Address` + The computed address of the new account. + """ + preimage = b"\xff" + address + salt + keccak256(call_data) + computed_address = keccak256(preimage) + canonical_address = computed_address[-20:] + padded_address = left_pad_zero_bytes(canonical_address, 20) + + return Address(padded_address) diff --git a/src/ethereum/osaka/utils/hexadecimal.py b/src/ethereum/osaka/utils/hexadecimal.py new file mode 100644 index 0000000000..39e392c137 --- /dev/null +++ b/src/ethereum/osaka/utils/hexadecimal.py @@ -0,0 +1,70 @@ +""" +Utility Functions For Hexadecimal Strings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Hexadecimal utility functions used in this specification, specific to +Osaka types. +""" +from ethereum_types.bytes import Bytes + +from ethereum.utils.hexadecimal import remove_hex_prefix + +from ..fork_types import Address, Bloom, Root + + +def hex_to_root(hex_string: str) -> Root: + """ + Convert hex string to trie root. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to trie root. + + Returns + ------- + root : `Root` + Trie root obtained from the given hexadecimal string. + """ + return Root(Bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_bloom(hex_string: str) -> Bloom: + """ + Convert hex string to bloom. + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to bloom. + + Returns + ------- + bloom : `Bloom` + Bloom obtained from the given hexadecimal string. + """ + return Bloom(Bytes.fromhex(remove_hex_prefix(hex_string))) + + +def hex_to_address(hex_string: str) -> Address: + """ + Convert hex string to Address (20 bytes). + + Parameters + ---------- + hex_string : + The hexadecimal string to be converted to Address. + + Returns + ------- + address : `Address` + The address obtained from the given hexadecimal string. + """ + return Address(Bytes.fromhex(remove_hex_prefix(hex_string).rjust(40, "0"))) diff --git a/src/ethereum/osaka/utils/message.py b/src/ethereum/osaka/utils/message.py new file mode 100644 index 0000000000..7efcc52950 --- /dev/null +++ b/src/ethereum/osaka/utils/message.py @@ -0,0 +1,99 @@ +""" +Hardfork Utility Functions For The Message Data-structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Message specific functions used in this osaka version of +specification. +""" +from ethereum_types.bytes import Bytes, Bytes0 +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.eoa_delegation import get_delegated_code_address +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from .address import compute_contract_address + + +def prepare_message( + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + tx: Transaction, +) -> Message: + """ + Execute a transaction against the provided environment. + + Parameters + ---------- + block_env : + Environment for the Ethereum Virtual Machine. + tx_env : + Environment for the transaction. + tx : + Transaction to be executed. + + Returns + ------- + message: `ethereum.osaka.vm.Message` + Items containing contract creation or message call specific data. + """ + accessed_addresses = set() + accessed_addresses.add(tx_env.origin) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.update(tx_env.access_list_addresses) + + disable_precompiles = False + + if isinstance(tx.to, Bytes0): + current_target = compute_contract_address( + tx_env.origin, + get_account(block_env.state, tx_env.origin).nonce - Uint(1), + ) + msg_data = Bytes(b"") + code = tx.data + code_address = None + elif isinstance(tx.to, Address): + current_target = tx.to + msg_data = tx.data + code = get_account(block_env.state, tx.to).code + + delegated_address = get_delegated_code_address(code) + if delegated_address is not None: + disable_precompiles = True + accessed_addresses.add(delegated_address) + code = get_account(block_env.state, delegated_address).code + + code_address = tx.to + else: + raise AssertionError("Target must be address or empty bytes") + + accessed_addresses.add(current_target) + + return Message( + block_env=block_env, + tx_env=tx_env, + caller=tx_env.origin, + target=tx.to, + gas=tx_env.gas, + value=tx.value, + data=msg_data, + code=code, + depth=Uint(0), + current_target=current_target, + code_address=code_address, + should_transfer_value=True, + is_static=False, + accessed_addresses=accessed_addresses, + accessed_storage_keys=set(tx_env.access_list_storage_keys), + disable_precompiles=disable_precompiles, + parent_evm=None, + ) diff --git a/src/ethereum/osaka/vm/__init__.py b/src/ethereum/osaka/vm/__init__.py new file mode 100644 index 0000000000..2211cfcb3e --- /dev/null +++ b/src/ethereum/osaka/vm/__init__.py @@ -0,0 +1,191 @@ +""" +Ethereum Virtual Machine (EVM) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +The abstract computer which runs the code stored in an +`.fork_types.Account`. +""" + +from dataclasses import dataclass, field +from typing import List, Optional, Set, Tuple, Union + +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 ..blocks import Log, Receipt, Withdrawal +from ..fork_types import Address, Authorization, VersionedHash +from ..state import State, TransientStorage +from ..transactions import LegacyTransaction +from ..trie import Trie + +__all__ = ("Environment", "Evm", "Message") + + +@dataclass +class BlockEnvironment: + """ + Items external to the virtual machine itself, provided by the environment. + """ + + chain_id: U64 + state: State + block_gas_limit: Uint + block_hashes: List[Hash32] + coinbase: Address + number: Uint + base_fee_per_gas: Uint + time: U256 + prev_randao: Bytes32 + excess_blob_gas: U64 + parent_beacon_block_root: Hash32 + + +@dataclass +class BlockOutput: + """ + Output from applying the block body to the present state. + + Contains the following: + + block_gas_used : `ethereum.base_types.Uint` + Gas used for executing all transactions. + transactions_trie : `ethereum.fork_types.Root` + Trie of all the transactions in the block. + receipts_trie : `ethereum.fork_types.Root` + Trie root of all the receipts in the block. + receipt_keys : + Key of all the receipts in the block. + block_logs : `Bloom` + Logs bloom of all the logs included in all the transactions of the + block. + withdrawals_trie : `ethereum.fork_types.Root` + Trie root of all the withdrawals in the block. + blob_gas_used : `ethereum.base_types.U64` + Total blob gas used in the block. + requests : `Bytes` + Hash of all the requests in the block. + """ + + block_gas_used: Uint = Uint(0) + transactions_trie: Trie[ + Bytes, Optional[Union[Bytes, LegacyTransaction]] + ] = field(default_factory=lambda: Trie(secured=False, default=None)) + receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + receipt_keys: Tuple[Bytes, ...] = field(default_factory=tuple) + block_logs: Tuple[Log, ...] = field(default_factory=tuple) + withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = field( + default_factory=lambda: Trie(secured=False, default=None) + ) + blob_gas_used: U64 = U64(0) + requests: List[Bytes] = field(default_factory=list) + + +@dataclass +class TransactionEnvironment: + """ + Items that are used by contract creation or message call. + """ + + origin: Address + gas_price: Uint + gas: Uint + access_list_addresses: Set[Address] + access_list_storage_keys: Set[Tuple[Address, Bytes32]] + transient_storage: TransientStorage + blob_versioned_hashes: Tuple[VersionedHash, ...] + authorizations: Tuple[Authorization, ...] + index_in_block: Optional[Uint] + tx_hash: Optional[Hash32] + traces: List[dict] + + +@dataclass +class Message: + """ + Items that are used by contract creation or message call. + """ + + block_env: BlockEnvironment + tx_env: TransactionEnvironment + caller: Address + target: Union[Bytes0, Address] + current_target: Address + gas: Uint + value: U256 + data: Bytes + code_address: Optional[Address] + code: Bytes + depth: Uint + should_transfer_value: bool + is_static: bool + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + disable_precompiles: bool + parent_evm: Optional["Evm"] + + +@dataclass +class Evm: + """The internal state of the virtual machine.""" + + pc: Uint + stack: List[U256] + memory: bytearray + code: Bytes + gas_left: Uint + valid_jump_destinations: Set[Uint] + logs: Tuple[Log, ...] + refund_counter: int + running: bool + message: Message + output: Bytes + accounts_to_delete: Set[Address] + return_data: Bytes + error: Optional[EthereumException] + accessed_addresses: Set[Address] + accessed_storage_keys: Set[Tuple[Address, Bytes32]] + + +def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of a successful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + evm.gas_left += child_evm.gas_left + evm.logs += child_evm.logs + evm.refund_counter += child_evm.refund_counter + evm.accounts_to_delete.update(child_evm.accounts_to_delete) + evm.accessed_addresses.update(child_evm.accessed_addresses) + evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) + + +def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: + """ + Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. + + Parameters + ---------- + evm : + The parent `EVM`. + child_evm : + The child evm to incorporate. + """ + evm.gas_left += child_evm.gas_left diff --git a/src/ethereum/osaka/vm/eoa_delegation.py b/src/ethereum/osaka/vm/eoa_delegation.py new file mode 100644 index 0000000000..3e7b7677ef --- /dev/null +++ b/src/ethereum/osaka/vm/eoa_delegation.py @@ -0,0 +1,215 @@ +""" +Set EOA account code. +""" + + +from typing import Optional, Tuple + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import keccak256 +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 + +SET_CODE_TX_MAGIC = b"\x05" +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 +PER_AUTH_BASE_COST = 12500 +NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") + + +def is_valid_delegation(code: bytes) -> bool: + """ + Whether the code is a valid delegation designation. + + Parameters + ---------- + code: `bytes` + The code to check. + + Returns + ------- + valid : `bool` + True if the code is a valid delegation designation, + False otherwise. + """ + if ( + len(code) == EOA_DELEGATED_CODE_LENGTH + and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER + ): + return True + return False + + +def get_delegated_code_address(code: bytes) -> Optional[Address]: + """ + Get the address to which the code delegates. + + Parameters + ---------- + code: `bytes` + The code to get the address from. + + Returns + ------- + address : `Optional[Address]` + The address of the delegated code. + """ + if is_valid_delegation(code): + return Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + return None + + +def recover_authority(authorization: Authorization) -> Address: + """ + Recover the authority address from the authorization. + + Parameters + ---------- + authorization + The authorization to recover the authority from. + + Raises + ------ + InvalidSignatureError + If the signature is invalid. + + Returns + ------- + authority : `Address` + The recovered authority address. + """ + y_parity, r, s = authorization.y_parity, authorization.r, authorization.s + if y_parity not in (0, 1): + raise InvalidSignatureError("Invalid y_parity in authorization") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("Invalid r value in authorization") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("Invalid s value in authorization") + + signing_hash = keccak256( + SET_CODE_TX_MAGIC + + rlp.encode( + ( + authorization.chain_id, + authorization.address, + authorization.nonce, + ) + ) + ) + + public_key = secp256k1_recover(r, s, U256(y_parity), signing_hash) + return Address(keccak256(public_key)[12:32]) + + +def access_delegation( + evm: Evm, address: Address +) -> Tuple[bool, Address, Bytes, Uint]: + """ + Get the delegation address, code, and the cost of access from the address. + + Parameters + ---------- + evm : `Evm` + The execution frame. + address : `Address` + The address to get the delegation from. + + Returns + ------- + 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 + if not is_valid_delegation(code): + return False, address, code, Uint(0) + + address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + code = get_account(state, address).code + + return True, address, code, access_gas_cost + + +def set_delegation(message: Message) -> U256: + """ + Set the delegation code for the authorities in the message. + + Parameters + ---------- + message : + Transaction specific items. + env : + External items required for EVM execution. + + Returns + ------- + refund_counter: `U256` + Refund from authority which already exists in state. + """ + state = message.block_env.state + refund_counter = U256(0) + for auth in message.tx_env.authorizations: + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + continue + + if auth.nonce >= U64.MAX_VALUE: + continue + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + continue + + message.accessed_addresses.add(authority) + + authority_account = get_account(state, authority) + authority_code = authority_account.code + + if authority_code and not is_valid_delegation(authority_code): + continue + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + continue + + if account_exists(state, 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) + + increment_nonce(state, authority) + + if message.code_address is None: + raise InvalidBlock("Invalid type 4 transaction: no target") + message.code = get_account(state, message.code_address).code + + if is_valid_delegation(message.code): + message.disable_precompiles = True + message.code_address = Address( + message.code[EOA_DELEGATION_MARKER_LENGTH:] + ) + message.accessed_addresses.add(message.code_address) + + message.code = get_account(state, message.code_address).code + + return refund_counter diff --git a/src/ethereum/osaka/vm/exceptions.py b/src/ethereum/osaka/vm/exceptions.py new file mode 100644 index 0000000000..2a4f2d2f65 --- /dev/null +++ b/src/ethereum/osaka/vm/exceptions.py @@ -0,0 +1,140 @@ +""" +Ethereum Virtual Machine (EVM) Exceptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Exceptions which cause the EVM to halt exceptionally. +""" + +from ethereum.exceptions import EthereumException + + +class ExceptionalHalt(EthereumException): + """ + Indicates that the EVM has experienced an exceptional halt. This causes + execution to immediately end with all gas being consumed. + """ + + +class Revert(EthereumException): + """ + Raised by the `REVERT` opcode. + + Unlike other EVM exceptions this does not result in the consumption of all + gas. + """ + + pass + + +class StackUnderflowError(ExceptionalHalt): + """ + Occurs when a pop is executed on an empty stack. + """ + + pass + + +class StackOverflowError(ExceptionalHalt): + """ + Occurs when a push is executed on a stack at max capacity. + """ + + pass + + +class OutOfGasError(ExceptionalHalt): + """ + Occurs when an operation costs more than the amount of gas left in the + frame. + """ + + pass + + +class InvalidOpcode(ExceptionalHalt): + """ + Raised when an invalid opcode is encountered. + """ + + code: int + + def __init__(self, code: int) -> None: + super().__init__(code) + self.code = code + + +class InvalidJumpDestError(ExceptionalHalt): + """ + Occurs when the destination of a jump operation doesn't meet any of the + following criteria: + + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + """ + + +class StackDepthLimitError(ExceptionalHalt): + """ + Raised when the message depth is greater than `1024` + """ + + pass + + +class WriteInStaticContext(ExceptionalHalt): + """ + Raised when an attempt is made to modify the state while operating inside + of a STATICCALL context. + """ + + pass + + +class OutOfBoundsRead(ExceptionalHalt): + """ + Raised when an attempt was made to read data beyond the + boundaries of the buffer. + """ + + pass + + +class InvalidParameter(ExceptionalHalt): + """ + Raised when invalid parameters are passed. + """ + + pass + + +class InvalidContractPrefix(ExceptionalHalt): + """ + Raised when the new contract code starts with 0xEF. + """ + + pass + + +class AddressCollision(ExceptionalHalt): + """ + Raised when the new contract address has a collision. + """ + + pass + + +class KZGProofError(ExceptionalHalt): + """ + Raised when the point evaluation precompile can't verify a proof. + """ + + pass diff --git a/src/ethereum/osaka/vm/gas.py b/src/ethereum/osaka/vm/gas.py new file mode 100644 index 0000000000..fd31799b55 --- /dev/null +++ b/src/ethereum/osaka/vm/gas.py @@ -0,0 +1,369 @@ +""" +Ethereum Virtual Machine (EVM) Gas +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM gas constants and calculators. +""" +from dataclasses import dataclass +from typing import List, Tuple + +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.trace import GasAndRefund, evm_trace +from ethereum.utils.numeric import ceil32, taylor_exponential + +from ..blocks import Header +from ..transactions import BlobTransaction, Transaction +from . import Evm +from .exceptions import OutOfGasError + +GAS_JUMPDEST = Uint(1) +GAS_BASE = Uint(2) +GAS_VERY_LOW = Uint(3) +GAS_STORAGE_SET = Uint(20000) +GAS_STORAGE_UPDATE = Uint(5000) +GAS_STORAGE_CLEAR_REFUND = Uint(4800) +GAS_LOW = Uint(5) +GAS_MID = Uint(8) +GAS_HIGH = Uint(10) +GAS_EXPONENTIATION = Uint(10) +GAS_EXPONENTIATION_PER_BYTE = Uint(50) +GAS_MEMORY = Uint(3) +GAS_KECCAK256 = Uint(30) +GAS_KECCAK256_WORD = Uint(6) +GAS_COPY = Uint(3) +GAS_BLOCK_HASH = Uint(20) +GAS_LOG = Uint(375) +GAS_LOG_DATA = Uint(8) +GAS_LOG_TOPIC = Uint(375) +GAS_CREATE = Uint(32000) +GAS_CODE_DEPOSIT = Uint(200) +GAS_ZERO = Uint(0) +GAS_NEW_ACCOUNT = Uint(25000) +GAS_CALL_VALUE = Uint(9000) +GAS_CALL_STIPEND = Uint(2300) +GAS_SELF_DESTRUCT = Uint(5000) +GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000) +GAS_ECRECOVER = Uint(3000) +GAS_SHA256 = Uint(60) +GAS_SHA256_WORD = Uint(12) +GAS_RIPEMD160 = Uint(600) +GAS_RIPEMD160_WORD = Uint(120) +GAS_IDENTITY = Uint(15) +GAS_IDENTITY_WORD = Uint(3) +GAS_RETURN_DATA_COPY = Uint(3) +GAS_FAST_STEP = Uint(5) +GAS_BLAKE2_PER_ROUND = Uint(1) +GAS_COLD_SLOAD = Uint(2100) +GAS_COLD_ACCOUNT_ACCESS = Uint(2600) +GAS_WARM_ACCESS = Uint(100) +GAS_INIT_CODE_WORD_COST = Uint(2) +GAS_BLOBHASH_OPCODE = Uint(3) +GAS_POINT_EVALUATION = Uint(50000) + +TARGET_BLOB_GAS_PER_BLOCK = U64(786432) +GAS_PER_BLOB = U64(2**17) +MIN_BLOB_GASPRICE = Uint(1) +BLOB_BASE_FEE_UPDATE_FRACTION = Uint(5007716) + +GAS_BLS_G1_ADD = Uint(375) +GAS_BLS_G1_MUL = Uint(12000) +GAS_BLS_G1_MAP = Uint(5500) +GAS_BLS_G2_ADD = Uint(600) +GAS_BLS_G2_MUL = Uint(22500) +GAS_BLS_G2_MAP = Uint(23800) + + +@dataclass +class ExtendMemory: + """ + Define the parameters for memory extension in opcodes + + `cost`: `ethereum.base_types.Uint` + The gas required to perform the extension + `expand_by`: `ethereum.base_types.Uint` + The size by which the memory will be extended + """ + + cost: Uint + expand_by: Uint + + +@dataclass +class MessageCallGas: + """ + Define the gas cost and stipend for executing the call opcodes. + + `cost`: `ethereum.base_types.Uint` + The non-refundable portion of gas reserved for executing the + call opcode. + `stipend`: `ethereum.base_types.Uint` + The portion of gas available to sub-calls that is refundable + if not consumed + """ + + cost: Uint + stipend: Uint + + +def charge_gas(evm: Evm, amount: Uint) -> None: + """ + Subtracts `amount` from `evm.gas_left`. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas the current operation requires. + + """ + evm_trace(evm, GasAndRefund(int(amount))) + + if evm.gas_left < amount: + raise OutOfGasError + else: + evm.gas_left -= amount + + +def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: + """ + Calculates the gas cost for allocating memory + to the smallest multiple of 32 bytes, + such that the allocated size is at least as big as the given size. + + Parameters + ---------- + size_in_bytes : + The size of the data in bytes. + + Returns + ------- + total_gas_cost : `ethereum.base_types.Uint` + The gas cost for storing data in memory. + """ + size_in_words = ceil32(size_in_bytes) // Uint(32) + linear_cost = size_in_words * GAS_MEMORY + quadratic_cost = size_in_words ** Uint(2) // Uint(512) + total_gas_cost = linear_cost + quadratic_cost + try: + return total_gas_cost + except ValueError: + raise OutOfGasError + + +def calculate_gas_extend_memory( + memory: bytearray, extensions: List[Tuple[U256, U256]] +) -> ExtendMemory: + """ + Calculates the gas amount to extend memory + + Parameters + ---------- + memory : + Memory contents of the EVM. + extensions: + List of extensions to be made to the memory. + Consists of a tuple of start position and size. + + Returns + ------- + extend_memory: `ExtendMemory` + """ + size_to_extend = Uint(0) + to_be_paid = Uint(0) + current_size = Uint(len(memory)) + for start_position, size in extensions: + if size == 0: + continue + before_size = ceil32(current_size) + after_size = ceil32(Uint(start_position) + Uint(size)) + if after_size <= before_size: + continue + + size_to_extend += after_size - before_size + already_paid = calculate_memory_gas_cost(before_size) + total_cost = calculate_memory_gas_cost(after_size) + to_be_paid += total_cost - already_paid + + current_size = after_size + + return ExtendMemory(to_be_paid, size_to_extend) + + +def calculate_message_call_gas( + value: U256, + gas: Uint, + gas_left: Uint, + memory_cost: Uint, + extra_gas: Uint, + call_stipend: Uint = GAS_CALL_STIPEND, +) -> MessageCallGas: + """ + Calculates the MessageCallGas (cost and stipend) for + executing call Opcodes. + + Parameters + ---------- + value: + The amount of `ETH` that needs to be transferred. + gas : + The amount of gas provided to the message-call. + gas_left : + The amount of gas left in the current frame. + memory_cost : + The amount needed to extend the memory in the current frame. + extra_gas : + The amount of gas needed for transferring value + creating a new + account inside a message call. + call_stipend : + The amount of stipend provided to a message call to execute code while + transferring value(ETH). + + Returns + ------- + message_call_gas: `MessageCallGas` + """ + call_stipend = Uint(0) if value == 0 else call_stipend + if gas_left < extra_gas + memory_cost: + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + gas = min(gas, max_message_call_gas(gas_left - memory_cost - extra_gas)) + + return MessageCallGas(gas + extra_gas, gas + call_stipend) + + +def max_message_call_gas(gas: Uint) -> Uint: + """ + Calculates the maximum gas that is allowed for making a message call + + Parameters + ---------- + gas : + The amount of gas provided to the message-call. + + Returns + ------- + max_allowed_message_call_gas: `ethereum.base_types.Uint` + The maximum gas allowed for making the message-call. + """ + return gas - (gas // Uint(64)) + + +def init_code_cost(init_code_length: Uint) -> Uint: + """ + Calculates the gas to be charged for the init code in CREAT* + opcodes as well as create transactions. + + Parameters + ---------- + init_code_length : + The length of the init code provided to the opcode + or a create transaction + + Returns + ------- + init_code_gas: `ethereum.base_types.Uint` + The gas to be charged for the init code. + """ + return GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) // Uint(32) + + +def calculate_excess_blob_gas(parent_header: Header) -> U64: + """ + Calculated the excess blob gas for the current block based + on the gas used in the parent block. + + Parameters + ---------- + parent_header : + The parent block of the current block. + + Returns + ------- + excess_blob_gas: `ethereum.base_types.U64` + The excess blob gas for the current block. + """ + # At the fork block, these are defined as zero. + excess_blob_gas = U64(0) + blob_gas_used = U64(0) + + if isinstance(parent_header, Header): + # After the fork block, read them from the parent header. + excess_blob_gas = parent_header.excess_blob_gas + blob_gas_used = parent_header.blob_gas_used + + parent_blob_gas = excess_blob_gas + blob_gas_used + if parent_blob_gas < TARGET_BLOB_GAS_PER_BLOCK: + return U64(0) + else: + return parent_blob_gas - TARGET_BLOB_GAS_PER_BLOCK + + +def calculate_total_blob_gas(tx: Transaction) -> U64: + """ + Calculate the total blob gas for a transaction. + + Parameters + ---------- + tx : + The transaction for which the blob gas is to be calculated. + + Returns + ------- + total_blob_gas: `ethereum.base_types.Uint` + The total blob gas for the transaction. + """ + if isinstance(tx, BlobTransaction): + return GAS_PER_BLOB * U64(len(tx.blob_versioned_hashes)) + else: + return U64(0) + + +def calculate_blob_gas_price(excess_blob_gas: U64) -> Uint: + """ + Calculate the blob gasprice for a block. + + Parameters + ---------- + excess_blob_gas : + The excess blob gas for the block. + + Returns + ------- + blob_gasprice: `Uint` + The blob gasprice. + """ + return taylor_exponential( + MIN_BLOB_GASPRICE, + Uint(excess_blob_gas), + BLOB_BASE_FEE_UPDATE_FRACTION, + ) + + +def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: + """ + Calculate the blob data fee for a transaction. + + Parameters + ---------- + excess_blob_gas : + The excess_blob_gas for the execution. + tx : + The transaction for which the blob data fee is to be calculated. + + Returns + ------- + data_fee: `Uint` + The blob data fee. + """ + return Uint(calculate_total_blob_gas(tx)) * calculate_blob_gas_price( + excess_blob_gas + ) diff --git a/src/ethereum/osaka/vm/instructions/__init__.py b/src/ethereum/osaka/vm/instructions/__init__.py new file mode 100644 index 0000000000..b220581c72 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/__init__.py @@ -0,0 +1,366 @@ +""" +EVM Instruction Encoding (Opcodes) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Machine readable representations of EVM instructions, and a mapping to their +implementations. +""" + +import enum +from typing import Callable, Dict + +from . import arithmetic as arithmetic_instructions +from . import bitwise as bitwise_instructions +from . import block as block_instructions +from . import comparison as comparison_instructions +from . import control_flow as control_flow_instructions +from . import environment as environment_instructions +from . import keccak as keccak_instructions +from . import log as log_instructions +from . import memory as memory_instructions +from . import stack as stack_instructions +from . import storage as storage_instructions +from . import system as system_instructions + + +class Ops(enum.Enum): + """ + Enum for EVM Opcodes + """ + + # Arithmetic Ops + ADD = 0x01 + MUL = 0x02 + SUB = 0x03 + DIV = 0x04 + SDIV = 0x05 + MOD = 0x06 + SMOD = 0x07 + ADDMOD = 0x08 + MULMOD = 0x09 + EXP = 0x0A + SIGNEXTEND = 0x0B + + # Comparison Ops + LT = 0x10 + GT = 0x11 + SLT = 0x12 + SGT = 0x13 + EQ = 0x14 + ISZERO = 0x15 + + # Bitwise Ops + AND = 0x16 + OR = 0x17 + XOR = 0x18 + NOT = 0x19 + BYTE = 0x1A + SHL = 0x1B + SHR = 0x1C + SAR = 0x1D + + # Keccak Op + KECCAK = 0x20 + + # Environmental Ops + ADDRESS = 0x30 + BALANCE = 0x31 + ORIGIN = 0x32 + CALLER = 0x33 + CALLVALUE = 0x34 + CALLDATALOAD = 0x35 + CALLDATASIZE = 0x36 + CALLDATACOPY = 0x37 + CODESIZE = 0x38 + CODECOPY = 0x39 + GASPRICE = 0x3A + EXTCODESIZE = 0x3B + EXTCODECOPY = 0x3C + RETURNDATASIZE = 0x3D + RETURNDATACOPY = 0x3E + EXTCODEHASH = 0x3F + + # Block Ops + BLOCKHASH = 0x40 + COINBASE = 0x41 + TIMESTAMP = 0x42 + NUMBER = 0x43 + PREVRANDAO = 0x44 + GASLIMIT = 0x45 + CHAINID = 0x46 + SELFBALANCE = 0x47 + BASEFEE = 0x48 + BLOBHASH = 0x49 + BLOBBASEFEE = 0x4A + + # Control Flow Ops + STOP = 0x00 + JUMP = 0x56 + JUMPI = 0x57 + PC = 0x58 + GAS = 0x5A + JUMPDEST = 0x5B + + # Storage Ops + SLOAD = 0x54 + SSTORE = 0x55 + TLOAD = 0x5C + TSTORE = 0x5D + + # Pop Operation + POP = 0x50 + + # Push Operations + PUSH0 = 0x5F + PUSH1 = 0x60 + PUSH2 = 0x61 + PUSH3 = 0x62 + PUSH4 = 0x63 + PUSH5 = 0x64 + PUSH6 = 0x65 + PUSH7 = 0x66 + PUSH8 = 0x67 + PUSH9 = 0x68 + PUSH10 = 0x69 + PUSH11 = 0x6A + PUSH12 = 0x6B + PUSH13 = 0x6C + PUSH14 = 0x6D + PUSH15 = 0x6E + PUSH16 = 0x6F + PUSH17 = 0x70 + PUSH18 = 0x71 + PUSH19 = 0x72 + PUSH20 = 0x73 + PUSH21 = 0x74 + PUSH22 = 0x75 + PUSH23 = 0x76 + PUSH24 = 0x77 + PUSH25 = 0x78 + PUSH26 = 0x79 + PUSH27 = 0x7A + PUSH28 = 0x7B + PUSH29 = 0x7C + PUSH30 = 0x7D + PUSH31 = 0x7E + PUSH32 = 0x7F + + # Dup operations + DUP1 = 0x80 + DUP2 = 0x81 + DUP3 = 0x82 + DUP4 = 0x83 + DUP5 = 0x84 + DUP6 = 0x85 + DUP7 = 0x86 + DUP8 = 0x87 + DUP9 = 0x88 + DUP10 = 0x89 + DUP11 = 0x8A + DUP12 = 0x8B + DUP13 = 0x8C + DUP14 = 0x8D + DUP15 = 0x8E + DUP16 = 0x8F + + # Swap operations + SWAP1 = 0x90 + SWAP2 = 0x91 + SWAP3 = 0x92 + SWAP4 = 0x93 + SWAP5 = 0x94 + SWAP6 = 0x95 + SWAP7 = 0x96 + SWAP8 = 0x97 + SWAP9 = 0x98 + SWAP10 = 0x99 + SWAP11 = 0x9A + SWAP12 = 0x9B + SWAP13 = 0x9C + SWAP14 = 0x9D + SWAP15 = 0x9E + SWAP16 = 0x9F + + # Memory Operations + MLOAD = 0x51 + MSTORE = 0x52 + MSTORE8 = 0x53 + MSIZE = 0x59 + MCOPY = 0x5E + + # Log Operations + LOG0 = 0xA0 + LOG1 = 0xA1 + LOG2 = 0xA2 + LOG3 = 0xA3 + LOG4 = 0xA4 + + # System Operations + CREATE = 0xF0 + CALL = 0xF1 + CALLCODE = 0xF2 + RETURN = 0xF3 + DELEGATECALL = 0xF4 + CREATE2 = 0xF5 + STATICCALL = 0xFA + REVERT = 0xFD + SELFDESTRUCT = 0xFF + + +op_implementation: Dict[Ops, Callable] = { + Ops.STOP: control_flow_instructions.stop, + Ops.ADD: arithmetic_instructions.add, + Ops.MUL: arithmetic_instructions.mul, + Ops.SUB: arithmetic_instructions.sub, + Ops.DIV: arithmetic_instructions.div, + Ops.SDIV: arithmetic_instructions.sdiv, + Ops.MOD: arithmetic_instructions.mod, + Ops.SMOD: arithmetic_instructions.smod, + Ops.ADDMOD: arithmetic_instructions.addmod, + Ops.MULMOD: arithmetic_instructions.mulmod, + Ops.EXP: arithmetic_instructions.exp, + Ops.SIGNEXTEND: arithmetic_instructions.signextend, + Ops.LT: comparison_instructions.less_than, + Ops.GT: comparison_instructions.greater_than, + Ops.SLT: comparison_instructions.signed_less_than, + Ops.SGT: comparison_instructions.signed_greater_than, + Ops.EQ: comparison_instructions.equal, + Ops.ISZERO: comparison_instructions.is_zero, + Ops.AND: bitwise_instructions.bitwise_and, + Ops.OR: bitwise_instructions.bitwise_or, + Ops.XOR: bitwise_instructions.bitwise_xor, + Ops.NOT: bitwise_instructions.bitwise_not, + Ops.BYTE: bitwise_instructions.get_byte, + Ops.SHL: bitwise_instructions.bitwise_shl, + Ops.SHR: bitwise_instructions.bitwise_shr, + Ops.SAR: bitwise_instructions.bitwise_sar, + Ops.KECCAK: keccak_instructions.keccak, + Ops.SLOAD: storage_instructions.sload, + Ops.BLOCKHASH: block_instructions.block_hash, + Ops.COINBASE: block_instructions.coinbase, + Ops.TIMESTAMP: block_instructions.timestamp, + Ops.NUMBER: block_instructions.number, + Ops.PREVRANDAO: block_instructions.prev_randao, + Ops.GASLIMIT: block_instructions.gas_limit, + Ops.CHAINID: block_instructions.chain_id, + Ops.MLOAD: memory_instructions.mload, + Ops.MSTORE: memory_instructions.mstore, + Ops.MSTORE8: memory_instructions.mstore8, + Ops.MSIZE: memory_instructions.msize, + Ops.MCOPY: memory_instructions.mcopy, + Ops.ADDRESS: environment_instructions.address, + Ops.BALANCE: environment_instructions.balance, + Ops.ORIGIN: environment_instructions.origin, + Ops.CALLER: environment_instructions.caller, + Ops.CALLVALUE: environment_instructions.callvalue, + Ops.CALLDATALOAD: environment_instructions.calldataload, + Ops.CALLDATASIZE: environment_instructions.calldatasize, + Ops.CALLDATACOPY: environment_instructions.calldatacopy, + Ops.CODESIZE: environment_instructions.codesize, + Ops.CODECOPY: environment_instructions.codecopy, + Ops.GASPRICE: environment_instructions.gasprice, + Ops.EXTCODESIZE: environment_instructions.extcodesize, + Ops.EXTCODECOPY: environment_instructions.extcodecopy, + Ops.RETURNDATASIZE: environment_instructions.returndatasize, + Ops.RETURNDATACOPY: environment_instructions.returndatacopy, + Ops.EXTCODEHASH: environment_instructions.extcodehash, + Ops.SELFBALANCE: environment_instructions.self_balance, + Ops.BASEFEE: environment_instructions.base_fee, + Ops.BLOBHASH: environment_instructions.blob_hash, + Ops.BLOBBASEFEE: environment_instructions.blob_base_fee, + Ops.SSTORE: storage_instructions.sstore, + Ops.TLOAD: storage_instructions.tload, + Ops.TSTORE: storage_instructions.tstore, + Ops.JUMP: control_flow_instructions.jump, + Ops.JUMPI: control_flow_instructions.jumpi, + Ops.PC: control_flow_instructions.pc, + Ops.GAS: control_flow_instructions.gas_left, + Ops.JUMPDEST: control_flow_instructions.jumpdest, + Ops.POP: stack_instructions.pop, + Ops.PUSH0: stack_instructions.push0, + Ops.PUSH1: stack_instructions.push1, + Ops.PUSH2: stack_instructions.push2, + Ops.PUSH3: stack_instructions.push3, + Ops.PUSH4: stack_instructions.push4, + Ops.PUSH5: stack_instructions.push5, + Ops.PUSH6: stack_instructions.push6, + Ops.PUSH7: stack_instructions.push7, + Ops.PUSH8: stack_instructions.push8, + Ops.PUSH9: stack_instructions.push9, + Ops.PUSH10: stack_instructions.push10, + Ops.PUSH11: stack_instructions.push11, + Ops.PUSH12: stack_instructions.push12, + Ops.PUSH13: stack_instructions.push13, + Ops.PUSH14: stack_instructions.push14, + Ops.PUSH15: stack_instructions.push15, + Ops.PUSH16: stack_instructions.push16, + Ops.PUSH17: stack_instructions.push17, + Ops.PUSH18: stack_instructions.push18, + Ops.PUSH19: stack_instructions.push19, + Ops.PUSH20: stack_instructions.push20, + Ops.PUSH21: stack_instructions.push21, + Ops.PUSH22: stack_instructions.push22, + Ops.PUSH23: stack_instructions.push23, + Ops.PUSH24: stack_instructions.push24, + Ops.PUSH25: stack_instructions.push25, + Ops.PUSH26: stack_instructions.push26, + Ops.PUSH27: stack_instructions.push27, + Ops.PUSH28: stack_instructions.push28, + Ops.PUSH29: stack_instructions.push29, + Ops.PUSH30: stack_instructions.push30, + Ops.PUSH31: stack_instructions.push31, + Ops.PUSH32: stack_instructions.push32, + Ops.DUP1: stack_instructions.dup1, + Ops.DUP2: stack_instructions.dup2, + Ops.DUP3: stack_instructions.dup3, + Ops.DUP4: stack_instructions.dup4, + Ops.DUP5: stack_instructions.dup5, + Ops.DUP6: stack_instructions.dup6, + Ops.DUP7: stack_instructions.dup7, + Ops.DUP8: stack_instructions.dup8, + Ops.DUP9: stack_instructions.dup9, + Ops.DUP10: stack_instructions.dup10, + Ops.DUP11: stack_instructions.dup11, + Ops.DUP12: stack_instructions.dup12, + Ops.DUP13: stack_instructions.dup13, + Ops.DUP14: stack_instructions.dup14, + Ops.DUP15: stack_instructions.dup15, + Ops.DUP16: stack_instructions.dup16, + Ops.SWAP1: stack_instructions.swap1, + Ops.SWAP2: stack_instructions.swap2, + Ops.SWAP3: stack_instructions.swap3, + Ops.SWAP4: stack_instructions.swap4, + Ops.SWAP5: stack_instructions.swap5, + Ops.SWAP6: stack_instructions.swap6, + Ops.SWAP7: stack_instructions.swap7, + Ops.SWAP8: stack_instructions.swap8, + Ops.SWAP9: stack_instructions.swap9, + Ops.SWAP10: stack_instructions.swap10, + Ops.SWAP11: stack_instructions.swap11, + Ops.SWAP12: stack_instructions.swap12, + Ops.SWAP13: stack_instructions.swap13, + Ops.SWAP14: stack_instructions.swap14, + Ops.SWAP15: stack_instructions.swap15, + Ops.SWAP16: stack_instructions.swap16, + Ops.LOG0: log_instructions.log0, + Ops.LOG1: log_instructions.log1, + Ops.LOG2: log_instructions.log2, + Ops.LOG3: log_instructions.log3, + Ops.LOG4: log_instructions.log4, + Ops.CREATE: system_instructions.create, + Ops.RETURN: system_instructions.return_, + Ops.CALL: system_instructions.call, + Ops.CALLCODE: system_instructions.callcode, + Ops.DELEGATECALL: system_instructions.delegatecall, + Ops.SELFDESTRUCT: system_instructions.selfdestruct, + Ops.STATICCALL: system_instructions.staticcall, + Ops.REVERT: system_instructions.revert, + Ops.CREATE2: system_instructions.create2, +} diff --git a/src/ethereum/osaka/vm/instructions/arithmetic.py b/src/ethereum/osaka/vm/instructions/arithmetic.py new file mode 100644 index 0000000000..28c97db189 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/arithmetic.py @@ -0,0 +1,374 @@ +""" +Ethereum Virtual Machine (EVM) Arithmetic Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Arithmetic instructions. +""" + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import get_sign + +from .. import Evm +from ..gas import ( + GAS_EXPONENTIATION, + GAS_EXPONENTIATION_PER_BYTE, + GAS_LOW, + GAS_MID, + GAS_VERY_LOW, + charge_gas, +) +from ..stack import pop, push + + +def add(evm: Evm) -> None: + """ + Adds the top two elements of the stack together, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_add(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sub(evm: Evm) -> None: + """ + Subtracts the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = x.wrapping_sub(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mul(evm: Evm) -> None: + """ + Multiply the top two elements of the stack, and pushes the result back + on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + result = x.wrapping_mul(y) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def div(evm: Evm) -> None: + """ + Integer division of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack) + divisor = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = U256(0) + else: + quotient = dividend // divisor + + push(evm.stack, quotient) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +U255_CEIL_VALUE = 2**255 + + +def sdiv(evm: Evm) -> None: + """ + Signed integer division of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + dividend = pop(evm.stack).to_signed() + divisor = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if divisor == 0: + quotient = 0 + elif dividend == -U255_CEIL_VALUE and divisor == -1: + quotient = -U255_CEIL_VALUE + else: + sign = get_sign(dividend * divisor) + quotient = sign * (abs(dividend) // abs(divisor)) + + push(evm.stack, U256.from_signed(quotient)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mod(evm: Evm) -> None: + """ + Modulo remainder of the top two elements of the stack. Pushes the result + back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = U256(0) + else: + remainder = x % y + + push(evm.stack, remainder) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def smod(evm: Evm) -> None: + """ + Signed modulo remainder of the top two elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack).to_signed() + y = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if y == 0: + remainder = 0 + else: + remainder = get_sign(x) * (abs(x) % abs(y)) + + push(evm.stack, U256.from_signed(remainder)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def addmod(evm: Evm) -> None: + """ + Modulo addition of the top 2 elements with the 3rd element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x + y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mulmod(evm: Evm) -> None: + """ + Modulo multiplication of the top 2 elements with the 3rd element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = Uint(pop(evm.stack)) + y = Uint(pop(evm.stack)) + z = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if z == 0: + result = U256(0) + else: + result = U256((x * y) % z) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def exp(evm: Evm) -> None: + """ + Exponential operation of the top 2 elements. Pushes the result back on + the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + base = Uint(pop(evm.stack)) + exponent = Uint(pop(evm.stack)) + + # GAS + # This is equivalent to 1 + floor(log(y, 256)). But in python the log + # function is inaccurate leading to wrong results. + exponent_bits = exponent.bit_length() + exponent_bytes = (exponent_bits + Uint(7)) // Uint(8) + charge_gas( + evm, GAS_EXPONENTIATION + GAS_EXPONENTIATION_PER_BYTE * exponent_bytes + ) + + # OPERATION + result = U256(pow(base, exponent, Uint(U256.MAX_VALUE) + Uint(1))) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signextend(evm: Evm) -> None: + """ + Sign extend operation. In other words, extend a signed number which + fits in N bytes to 32 bytes. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_num = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_LOW) + + # OPERATION + if byte_num > U256(31): + # Can't extend any further + result = value + else: + # U256(0).to_be_bytes() gives b'' instead b'\x00'. + value_bytes = Bytes(value.to_be_bytes32()) + # Now among the obtained value bytes, consider only + # N `least significant bytes`, where N is `byte_num + 1`. + value_bytes = value_bytes[31 - int(byte_num) :] + sign_bit = value_bytes[0] >> 7 + if sign_bit == 0: + result = U256.from_be_bytes(value_bytes) + else: + num_bytes_prepend = U256(32) - (byte_num + U256(1)) + result = U256.from_be_bytes( + bytearray([0xFF] * num_bytes_prepend) + value_bytes + ) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/bitwise.py b/src/ethereum/osaka/vm/instructions/bitwise.py new file mode 100644 index 0000000000..3abb58be48 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/bitwise.py @@ -0,0 +1,240 @@ +""" +Ethereum Virtual Machine (EVM) Bitwise Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM bitwise instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def bitwise_and(evm: Evm) -> None: + """ + Bitwise AND operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x & y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_or(evm: Evm) -> None: + """ + Bitwise OR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x | y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_xor(evm: Evm) -> None: + """ + Bitwise XOR operation of the top 2 elements of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + y = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, x ^ y) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_not(evm: Evm) -> None: + """ + Bitwise NOT operation of the top element of the stack. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + push(evm.stack, ~x) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def get_byte(evm: Evm) -> None: + """ + For a word (defined by next top element of the stack), retrieve the + Nth byte (0-indexed and defined by top element of stack) from the + left (most significant) to right (least significant). + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + byte_index = pop(evm.stack) + word = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if byte_index >= U256(32): + result = U256(0) + else: + extra_bytes_to_right = U256(31) - byte_index + # Remove the extra bytes in the right + word = word >> (extra_bytes_to_right * U256(8)) + # Remove the extra bytes in the left + word = word & U256(0xFF) + result = word + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shl(evm: Evm) -> None: + """ + Logical shift left (SHL) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = Uint(pop(evm.stack)) + value = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < Uint(256): + result = U256((value << shift) & Uint(U256.MAX_VALUE)) + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_shr(evm: Evm) -> None: + """ + Logical shift right (SHR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = pop(evm.stack) + value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < U256(256): + result = value >> shift + else: + result = U256(0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def bitwise_sar(evm: Evm) -> None: + """ + Arithmetic shift right (SAR) operation of the top 2 elements of the stack. + Pushes the result back on the stack. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + shift = int(pop(evm.stack)) + signed_value = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + if shift < 256: + result = U256.from_signed(signed_value >> shift) + elif signed_value >= 0: + result = U256(0) + else: + result = U256.MAX_VALUE + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/block.py b/src/ethereum/osaka/vm/instructions/block.py new file mode 100644 index 0000000000..80644000fd --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/block.py @@ -0,0 +1,255 @@ +""" +Ethereum Virtual Machine (EVM) Block Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM block instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_BASE, GAS_BLOCK_HASH, charge_gas +from ..stack import pop, push + + +def block_hash(evm: Evm) -> None: + """ + Push the hash of one of the 256 most recent complete blocks onto the + stack. The block number to hash is present at the top of the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackUnderflowError` + If `len(stack)` is less than `1`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `20`. + """ + # STACK + block_number = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_BLOCK_HASH) + + # OPERATION + max_block_number = block_number + Uint(256) + current_block_number = evm.message.block_env.number + if ( + current_block_number <= block_number + or current_block_number > max_block_number + ): + # Default hash to 0, if the block of interest is not yet on the chain + # (including the block which has the current executing transaction), + # or if the block's age is more than 256. + hash = b"\x00" + else: + hash = evm.message.block_env.block_hashes[ + -(current_block_number - block_number) + ] + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def coinbase(evm: Evm) -> None: + """ + Push the current block's beneficiary address (address of the block miner) + onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def timestamp(evm: Evm) -> None: + """ + Push the current block's timestamp onto the stack. Here the timestamp + being referred is actually the unix timestamp in seconds. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.message.block_env.time) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def number(evm: Evm) -> None: + """ + Push the current block's number onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.number)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def prev_randao(evm: Evm) -> None: + """ + Push the `prev_randao` value onto the stack. + + The `prev_randao` value is the random output of the beacon chain's + randomness oracle for the previous block. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_limit(evm: Evm) -> None: + """ + Push the current block's gas limit onto the stack. + + Here the current block refers to the block in which the currently + executing transaction/call resides. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.block_gas_limit)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def chain_id(evm: Evm) -> None: + """ + Push the chain id onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + :py:class:`~ethereum.osaka.vm.exceptions.StackOverflowError` + If `len(stack)` is equal to `1024`. + :py:class:`~ethereum.osaka.vm.exceptions.OutOfGasError` + If `evm.gas_left` is less than `2`. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.chain_id)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/comparison.py b/src/ethereum/osaka/vm/instructions/comparison.py new file mode 100644 index 0000000000..275455ba53 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/comparison.py @@ -0,0 +1,178 @@ +""" +Ethereum Virtual Machine (EVM) Comparison Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Comparison instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from .. import Evm +from ..gas import GAS_VERY_LOW, charge_gas +from ..stack import pop, push + + +def less_than(evm: Evm) -> None: + """ + Checks if the top element is less than the next top element. Pushes the + result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_less_than(evm: Evm) -> None: + """ + Signed less-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left < right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def greater_than(evm: Evm) -> None: + """ + Checks if the top element is greater than the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def signed_greater_than(evm: Evm) -> None: + """ + Signed greater-than comparison. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack).to_signed() + right = pop(evm.stack).to_signed() + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left > right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def equal(evm: Evm) -> None: + """ + Checks if the top element is equal to the next top element. Pushes + the result back on the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + left = pop(evm.stack) + right = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(left == right) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def is_zero(evm: Evm) -> None: + """ + Checks if the top element is equal to 0. Pushes the result back on the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + x = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + result = U256(x == 0) + + push(evm.stack, result) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/control_flow.py b/src/ethereum/osaka/vm/instructions/control_flow.py new file mode 100644 index 0000000000..7722661f79 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/control_flow.py @@ -0,0 +1,171 @@ +""" +Ethereum Virtual Machine (EVM) Control Flow Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM control flow instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ...vm.gas import GAS_BASE, GAS_HIGH, GAS_JUMPDEST, GAS_MID, charge_gas +from .. import Evm +from ..exceptions import InvalidJumpDestError +from ..stack import pop, push + + +def stop(evm: Evm) -> None: + """ + Stop further execution of EVM code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + pass + + # OPERATION + evm.running = False + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jump(evm: Evm) -> None: + """ + Alter the program counter to the location specified by the top of the + stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + + # GAS + charge_gas(evm, GAS_MID) + + # OPERATION + if jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + + # PROGRAM COUNTER + evm.pc = Uint(jump_dest) + + +def jumpi(evm: Evm) -> None: + """ + Alter the program counter to the specified location if and only if a + condition is true. If the condition is not true, then the program counter + would increase only by 1. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + jump_dest = Uint(pop(evm.stack)) + conditional_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_HIGH) + + # OPERATION + if conditional_value == 0: + destination = evm.pc + Uint(1) + elif jump_dest not in evm.valid_jump_destinations: + raise InvalidJumpDestError + else: + destination = jump_dest + + # PROGRAM COUNTER + evm.pc = destination + + +def pc(evm: Evm) -> None: + """ + Push onto the stack the value of the program counter after reaching the + current instruction and without increasing it for the next instruction. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.pc)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gas_left(evm: Evm) -> None: + """ + Push the amount of available gas (including the corresponding reduction + for the cost of this instruction) onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.gas_left)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def jumpdest(evm: Evm) -> None: + """ + Mark a valid destination for jumps. This is a noop, present only + to be used by `JUMP` and `JUMPI` opcodes to verify that their jump is + valid. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_JUMPDEST) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/environment.py b/src/ethereum/osaka/vm/instructions/environment.py new file mode 100644 index 0000000000..5ddd12dac8 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/environment.py @@ -0,0 +1,597 @@ +""" +Ethereum Virtual Machine (EVM) Environmental Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM environment related instructions. +""" + +from ethereum_types.bytes import Bytes32 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from ...fork_types import EMPTY_ACCOUNT +from ...state import get_account +from ...utils.address import to_address +from ...vm.memory import buffer_read, memory_write +from .. import Evm +from ..exceptions import OutOfBoundsRead +from ..gas import ( + GAS_BASE, + GAS_BLOBHASH_OPCODE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_COPY, + GAS_FAST_STEP, + GAS_RETURN_DATA_COPY, + GAS_VERY_LOW, + GAS_WARM_ACCESS, + calculate_blob_gas_price, + calculate_gas_extend_memory, + charge_gas, +) +from ..stack import pop, push + + +def address(evm: Evm) -> None: + """ + Pushes the address of the current executing account to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.current_target)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def balance(evm: Evm) -> None: + """ + Pushes the balance of the given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_addresses.add(address) + charge_gas(evm, GAS_COLD_ACCOUNT_ACCESS) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account(evm.message.block_env.state, address).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def origin(evm: Evm) -> None: + """ + Pushes the address of the original transaction sender to the stack. + The origin address can only be an EOA. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def caller(evm: Evm) -> None: + """ + Pushes the address of the caller onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256.from_be_bytes(evm.message.caller)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callvalue(evm: Evm) -> None: + """ + Push the value (in wei) sent with the call onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, evm.message.value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldataload(evm: Evm) -> None: + """ + Push a word (32 bytes) of the input data belonging to the current + environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + value = buffer_read(evm.message.data, start_index, U256(32)) + + push(evm.stack, U256.from_be_bytes(value)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatasize(evm: Evm) -> None: + """ + Push the size of input data in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.message.data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def calldatacopy(evm: Evm) -> None: + """ + Copy a portion of the input data in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + data_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.message.data, data_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codesize(evm: Evm) -> None: + """ + Push the size of code running in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.code))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def codecopy(evm: Evm) -> None: + """ + Copy a portion of the code in current environment to memory. + + This will also expand the memory, in case that the memory is insufficient + to store the data. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = buffer_read(evm.code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def gasprice(evm: Evm) -> None: + """ + Push the gas price used in current environment onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.tx_env.gas_price)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodesize(evm: Evm) -> None: + """ + Push the code size of a given account onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) + + # OPERATION + code = get_account(evm.message.block_env.state, address).code + + codesize = U256(len(code)) + push(evm.stack, codesize) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodecopy(evm: Evm) -> None: + """ + Copy a portion of an account's code to memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + address = to_address(pop(evm.stack)) + memory_start_index = pop(evm.stack) + code_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + code = get_account(evm.message.block_env.state, address).code + + value = buffer_read(code, code_start_index, size) + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatasize(evm: Evm) -> None: + """ + Pushes the size of the return data buffer onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.return_data))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def returndatacopy(evm: Evm) -> None: + """ + Copies data from the return data buffer code to memory + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + return_data_start_position = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + copy_gas_cost = GAS_RETURN_DATA_COPY * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + if Uint(return_data_start_position) + Uint(size) > ulen(evm.return_data): + raise OutOfBoundsRead + + evm.memory += b"\x00" * extend_memory.expand_by + value = evm.return_data[ + return_data_start_position : return_data_start_position + size + ] + memory_write(evm.memory, memory_start_index, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def extcodehash(evm: Evm) -> None: + """ + Returns the keccak256 hash of a contract’s bytecode + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + address = to_address(pop(evm.stack)) + + # GAS + if address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + charge_gas(evm, access_gas_cost) + + # OPERATION + account = get_account(evm.message.block_env.state, address) + + if account == EMPTY_ACCOUNT: + codehash = U256(0) + else: + code = account.code + codehash = U256.from_be_bytes(keccak256(code)) + + push(evm.stack, codehash) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def self_balance(evm: Evm) -> None: + """ + Pushes the balance of the current address to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_FAST_STEP) + + # OPERATION + # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. + balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + + push(evm.stack, balance) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def base_fee(evm: Evm) -> None: + """ + Pushes the base fee of the current block on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_hash(evm: Evm) -> None: + """ + Pushes the versioned hash at a particular index on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + index = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BLOBHASH_OPCODE) + + # OPERATION + if int(index) < len(evm.message.tx_env.blob_versioned_hashes): + blob_hash = evm.message.tx_env.blob_versioned_hashes[index] + else: + blob_hash = Bytes32(b"\x00" * 32) + push(evm.stack, U256.from_be_bytes(blob_hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def blob_base_fee(evm: Evm) -> None: + """ + Pushes the blob base fee on to the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + blob_base_fee = calculate_blob_gas_price( + evm.message.block_env.excess_blob_gas + ) + push(evm.stack, U256(blob_base_fee)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/keccak.py b/src/ethereum/osaka/vm/instructions/keccak.py new file mode 100644 index 0000000000..830d368277 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/keccak.py @@ -0,0 +1,64 @@ +""" +Ethereum Virtual Machine (EVM) Keccak Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM keccak instructions. +""" + +from ethereum_types.numeric import U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_KECCAK256, + GAS_KECCAK256_WORD, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop, push + + +def keccak(evm: Evm) -> None: + """ + Pushes to the stack the Keccak-256 hash of a region of memory. + + This also expands the memory, in case the memory is insufficient to + access the data's memory location. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + words = ceil32(Uint(size)) // Uint(32) + word_gas_cost = GAS_KECCAK256_WORD * words + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas(evm, GAS_KECCAK256 + word_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + data = memory_read_bytes(evm.memory, memory_start_index, size) + hash = keccak256(data) + + push(evm.stack, U256.from_be_bytes(hash)) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/log.py b/src/ethereum/osaka/vm/instructions/log.py new file mode 100644 index 0000000000..87c06ed6be --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/log.py @@ -0,0 +1,88 @@ +""" +Ethereum Virtual Machine (EVM) Logging Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM logging instructions. +""" +from functools import partial + +from ethereum_types.numeric import Uint + +from ...blocks import Log +from .. import Evm +from ..exceptions import WriteInStaticContext +from ..gas import ( + GAS_LOG, + GAS_LOG_DATA, + GAS_LOG_TOPIC, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes +from ..stack import pop + + +def log_n(evm: Evm, num_topics: int) -> None: + """ + Appends a log entry, having `num_topics` topics, to the evm logs. + + This will also expand the memory if the data (required by the log entry) + corresponding to the memory is not accessible. + + Parameters + ---------- + evm : + The current EVM frame. + num_topics : + The number of topics to be included in the log entry. + + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + topics = [] + for _ in range(num_topics): + topic = pop(evm.stack).to_be_bytes32() + topics.append(topic) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + charge_gas( + evm, + GAS_LOG + + GAS_LOG_DATA * Uint(size) + + GAS_LOG_TOPIC * Uint(num_topics) + + extend_memory.cost, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + if evm.message.is_static: + raise WriteInStaticContext + log_entry = Log( + address=evm.message.current_target, + topics=tuple(topics), + data=memory_read_bytes(evm.memory, memory_start_index, size), + ) + + evm.logs = evm.logs + (log_entry,) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +log0 = partial(log_n, num_topics=0) +log1 = partial(log_n, num_topics=1) +log2 = partial(log_n, num_topics=2) +log3 = partial(log_n, num_topics=3) +log4 = partial(log_n, num_topics=4) diff --git a/src/ethereum/osaka/vm/instructions/memory.py b/src/ethereum/osaka/vm/instructions/memory.py new file mode 100644 index 0000000000..89533af37e --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/memory.py @@ -0,0 +1,177 @@ +""" +Ethereum Virtual Machine (EVM) Memory Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM Memory instructions. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.numeric import ceil32 + +from .. import Evm +from ..gas import ( + GAS_BASE, + GAS_COPY, + GAS_VERY_LOW, + calculate_gas_extend_memory, + charge_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def mstore(evm: Evm) -> None: + """ + Stores a word to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(len(value)))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + memory_write(evm.memory, start_position, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mstore8(evm: Evm) -> None: + """ + Stores a byte to memory. + This also expands the memory, if the memory is + insufficient to store the word. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + value = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(1))] + ) + + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + normalized_bytes_value = Bytes([value & U256(0xFF)]) + memory_write(evm.memory, start_position, normalized_bytes_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mload(evm: Evm) -> None: + """ + Load word from memory. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + start_position = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(start_position, U256(32))] + ) + charge_gas(evm, GAS_VERY_LOW + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = U256.from_be_bytes( + memory_read_bytes(evm.memory, start_position, U256(32)) + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def msize(evm: Evm) -> None: + """ + Push the size of active memory in bytes onto the stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + push(evm.stack, U256(len(evm.memory))) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def mcopy(evm: Evm) -> None: + """ + Copy the bytes in memory from one location to another. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + destination = pop(evm.stack) + source = pop(evm.stack) + length = pop(evm.stack) + + # GAS + words = ceil32(Uint(length)) // Uint(32) + copy_gas_cost = GAS_COPY * words + + extend_memory = calculate_gas_extend_memory( + evm.memory, [(source, length), (destination, length)] + ) + charge_gas(evm, GAS_VERY_LOW + copy_gas_cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + value = memory_read_bytes(evm.memory, source, length) + memory_write(evm.memory, destination, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) diff --git a/src/ethereum/osaka/vm/instructions/stack.py b/src/ethereum/osaka/vm/instructions/stack.py new file mode 100644 index 0000000000..2e8a492412 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/stack.py @@ -0,0 +1,209 @@ +""" +Ethereum Virtual Machine (EVM) Stack Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM stack related instructions. +""" + +from functools import partial + +from ethereum_types.numeric import U256, Uint + +from .. import Evm, stack +from ..exceptions import StackUnderflowError +from ..gas import GAS_BASE, GAS_VERY_LOW, charge_gas +from ..memory import buffer_read + + +def pop(evm: Evm) -> None: + """ + Remove item from stack. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + stack.pop(evm.stack) + + # GAS + charge_gas(evm, GAS_BASE) + + # OPERATION + pass + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def push_n(evm: Evm, num_bytes: int) -> None: + """ + Pushes a N-byte immediate onto the stack. Push zero if num_bytes is zero. + + Parameters + ---------- + evm : + The current EVM frame. + + num_bytes : + The number of immediate bytes to be read from the code and pushed to + the stack. Push zero if num_bytes is zero. + + """ + # STACK + pass + + # GAS + if num_bytes == 0: + charge_gas(evm, GAS_BASE) + else: + charge_gas(evm, GAS_VERY_LOW) + + # OPERATION + data_to_push = U256.from_be_bytes( + buffer_read(evm.code, U256(evm.pc + Uint(1)), U256(num_bytes)) + ) + stack.push(evm.stack, data_to_push) + + # PROGRAM COUNTER + evm.pc += Uint(1) + Uint(num_bytes) + + +def dup_n(evm: Evm, item_number: int) -> None: + """ + Duplicate the Nth stack item (from top of the stack) to the top of stack. + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be duplicated + to the top of stack. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + data_to_duplicate = evm.stack[len(evm.stack) - 1 - item_number] + stack.push(evm.stack, data_to_duplicate) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def swap_n(evm: Evm, item_number: int) -> None: + """ + Swap the top and the `item_number` element of the stack, where + the top of the stack is position zero. + + If `item_number` is zero, this function does nothing (which should not be + possible, since there is no `SWAP0` instruction). + + Parameters + ---------- + evm : + The current EVM frame. + + item_number : + The stack item number (0-indexed from top of stack) to be swapped + with the top of stack element. + + """ + # STACK + pass + + # GAS + charge_gas(evm, GAS_VERY_LOW) + if item_number >= len(evm.stack): + raise StackUnderflowError + evm.stack[-1], evm.stack[-1 - item_number] = ( + evm.stack[-1 - item_number], + evm.stack[-1], + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +push0 = partial(push_n, num_bytes=0) +push1 = partial(push_n, num_bytes=1) +push2 = partial(push_n, num_bytes=2) +push3 = partial(push_n, num_bytes=3) +push4 = partial(push_n, num_bytes=4) +push5 = partial(push_n, num_bytes=5) +push6 = partial(push_n, num_bytes=6) +push7 = partial(push_n, num_bytes=7) +push8 = partial(push_n, num_bytes=8) +push9 = partial(push_n, num_bytes=9) +push10 = partial(push_n, num_bytes=10) +push11 = partial(push_n, num_bytes=11) +push12 = partial(push_n, num_bytes=12) +push13 = partial(push_n, num_bytes=13) +push14 = partial(push_n, num_bytes=14) +push15 = partial(push_n, num_bytes=15) +push16 = partial(push_n, num_bytes=16) +push17 = partial(push_n, num_bytes=17) +push18 = partial(push_n, num_bytes=18) +push19 = partial(push_n, num_bytes=19) +push20 = partial(push_n, num_bytes=20) +push21 = partial(push_n, num_bytes=21) +push22 = partial(push_n, num_bytes=22) +push23 = partial(push_n, num_bytes=23) +push24 = partial(push_n, num_bytes=24) +push25 = partial(push_n, num_bytes=25) +push26 = partial(push_n, num_bytes=26) +push27 = partial(push_n, num_bytes=27) +push28 = partial(push_n, num_bytes=28) +push29 = partial(push_n, num_bytes=29) +push30 = partial(push_n, num_bytes=30) +push31 = partial(push_n, num_bytes=31) +push32 = partial(push_n, num_bytes=32) + +dup1 = partial(dup_n, item_number=0) +dup2 = partial(dup_n, item_number=1) +dup3 = partial(dup_n, item_number=2) +dup4 = partial(dup_n, item_number=3) +dup5 = partial(dup_n, item_number=4) +dup6 = partial(dup_n, item_number=5) +dup7 = partial(dup_n, item_number=6) +dup8 = partial(dup_n, item_number=7) +dup9 = partial(dup_n, item_number=8) +dup10 = partial(dup_n, item_number=9) +dup11 = partial(dup_n, item_number=10) +dup12 = partial(dup_n, item_number=11) +dup13 = partial(dup_n, item_number=12) +dup14 = partial(dup_n, item_number=13) +dup15 = partial(dup_n, item_number=14) +dup16 = partial(dup_n, item_number=15) + +swap1 = partial(swap_n, item_number=1) +swap2 = partial(swap_n, item_number=2) +swap3 = partial(swap_n, item_number=3) +swap4 = partial(swap_n, item_number=4) +swap5 = partial(swap_n, item_number=5) +swap6 = partial(swap_n, item_number=6) +swap7 = partial(swap_n, item_number=7) +swap8 = partial(swap_n, item_number=8) +swap9 = partial(swap_n, item_number=9) +swap10 = partial(swap_n, item_number=10) +swap11 = partial(swap_n, item_number=11) +swap12 = partial(swap_n, item_number=12) +swap13 = partial(swap_n, item_number=13) +swap14 = partial(swap_n, item_number=14) +swap15 = partial(swap_n, item_number=15) +swap16 = partial(swap_n, item_number=16) diff --git a/src/ethereum/osaka/vm/instructions/storage.py b/src/ethereum/osaka/vm/instructions/storage.py new file mode 100644 index 0000000000..65a0d5a9b6 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/storage.py @@ -0,0 +1,184 @@ +""" +Ethereum Virtual Machine (EVM) Storage Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM storage related instructions. +""" +from ethereum_types.numeric import Uint + +from ...state import ( + get_storage, + get_storage_original, + get_transient_storage, + set_storage, + set_transient_storage, +) +from .. import Evm +from ..exceptions import OutOfGasError, WriteInStaticContext +from ..gas import ( + GAS_CALL_STIPEND, + GAS_COLD_SLOAD, + GAS_STORAGE_CLEAR_REFUND, + GAS_STORAGE_SET, + GAS_STORAGE_UPDATE, + GAS_WARM_ACCESS, + charge_gas, +) +from ..stack import pop, push + + +def sload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + storage of the current account. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + if (evm.message.current_target, key) in evm.accessed_storage_keys: + charge_gas(evm, GAS_WARM_ACCESS) + else: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + charge_gas(evm, GAS_COLD_SLOAD) + + # OPERATION + value = get_storage( + evm.message.block_env.state, evm.message.current_target, key + ) + + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def sstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's storage. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + 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 + ) + current_value = get_storage(state, evm.message.current_target, key) + + gas_cost = Uint(0) + + if (evm.message.current_target, key) not in evm.accessed_storage_keys: + evm.accessed_storage_keys.add((evm.message.current_target, key)) + gas_cost += GAS_COLD_SLOAD + + if original_value == current_value and current_value != new_value: + if original_value == 0: + gas_cost += GAS_STORAGE_SET + else: + gas_cost += GAS_STORAGE_UPDATE - GAS_COLD_SLOAD + else: + gas_cost += GAS_WARM_ACCESS + + # Refund Counter Calculation + if current_value != new_value: + if original_value != 0 and current_value != 0 and new_value == 0: + # Storage is cleared for the first time in the transaction + evm.refund_counter += int(GAS_STORAGE_CLEAR_REFUND) + + if original_value != 0 and current_value == 0: + # Gas refund issued earlier to be reversed + evm.refund_counter -= int(GAS_STORAGE_CLEAR_REFUND) + + if original_value == new_value: + # Storage slot being restored to its original value + if original_value == 0: + # Slot was originally empty and was SET earlier + evm.refund_counter += int(GAS_STORAGE_SET - GAS_WARM_ACCESS) + else: + # Slot was originally non-empty and was UPDATED earlier + evm.refund_counter += int( + GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS + ) + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + set_storage(state, evm.message.current_target, key, new_value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tload(evm: Evm) -> None: + """ + Loads to the stack, the value corresponding to a certain key from the + transient storage of the current account. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + + # OPERATION + value = get_transient_storage( + evm.message.tx_env.transient_storage, evm.message.current_target, key + ) + push(evm.stack, value) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def tstore(evm: Evm) -> None: + """ + Stores a value at a certain key in the current context's transient storage. + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + key = pop(evm.stack).to_be_bytes32() + new_value = pop(evm.stack) + + # GAS + charge_gas(evm, GAS_WARM_ACCESS) + if evm.message.is_static: + raise WriteInStaticContext + set_transient_storage( + evm.message.tx_env.transient_storage, + 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 new file mode 100644 index 0000000000..ea9be98391 --- /dev/null +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -0,0 +1,744 @@ +""" +Ethereum Virtual Machine (EVM) System Instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementations of the EVM system related instructions. +""" + +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import U256, Uint + +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, + to_address, +) +from ...vm.eoa_delegation import access_delegation +from .. import ( + Evm, + Message, + incorporate_child_on_error, + incorporate_child_on_success, +) +from ..exceptions import OutOfGasError, Revert, WriteInStaticContext +from ..gas import ( + GAS_CALL_VALUE, + GAS_COLD_ACCOUNT_ACCESS, + GAS_CREATE, + GAS_KECCAK256_WORD, + GAS_NEW_ACCOUNT, + GAS_SELF_DESTRUCT, + GAS_SELF_DESTRUCT_NEW_ACCOUNT, + GAS_WARM_ACCESS, + GAS_ZERO, + calculate_gas_extend_memory, + calculate_message_call_gas, + charge_gas, + init_code_cost, + max_message_call_gas, +) +from ..memory import memory_read_bytes, memory_write +from ..stack import pop, push + + +def generic_create( + evm: Evm, + endowment: U256, + contract_address: Address, + memory_start_position: U256, + memory_size: U256, +) -> None: + """ + Core logic used by the `CREATE*` family of opcodes. + """ + # This import causes a circular import error + # if it's not moved inside this method + from ...vm.interpreter import ( + MAX_CODE_SIZE, + STACK_DEPTH_LIMIT, + process_create_message, + ) + + call_data = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + if len(call_data) > 2 * MAX_CODE_SIZE: + raise OutOfGasError + + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) + evm.gas_left -= create_message_gas + if evm.message.is_static: + raise WriteInStaticContext + evm.return_data = b"" + + sender_address = evm.message.current_target + sender = get_account(evm.message.block_env.state, sender_address) + + if ( + sender.balance < endowment + or sender.nonce == Uint(2**64 - 1) + or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT + ): + evm.gas_left += create_message_gas + push(evm.stack, U256(0)) + return + + 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 + ) + push(evm.stack, U256(0)) + return + + increment_nonce(evm.message.block_env.state, evm.message.current_target) + + child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, + caller=evm.message.current_target, + target=Bytes0(), + gas=create_message_gas, + value=endowment, + data=b"", + code=call_data, + current_target=contract_address, + depth=evm.message.depth + Uint(1), + code_address=None, + should_transfer_value=True, + is_static=False, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + disable_precompiles=False, + parent_evm=evm, + ) + child_evm = process_create_message(child_message) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = b"" + push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) + + +def create(evm: Evm) -> None: + """ + Creates a new account with associated code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + init_code_gas = init_code_cost(Uint(memory_size)) + + charge_gas(evm, GAS_CREATE + extend_memory.cost + init_code_gas) + + # OPERATION + 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, + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def create2(evm: Evm) -> None: + """ + Creates a new account with associated code. + + It's similar to CREATE opcode except that the address of new account + depends on the init_code instead of the nonce of sender. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + endowment = pop(evm.stack) + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + salt = pop(evm.stack).to_be_bytes32() + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + call_data_words = ceil32(Uint(memory_size)) // Uint(32) + init_code_gas = init_code_cost(Uint(memory_size)) + charge_gas( + evm, + GAS_CREATE + + GAS_KECCAK256_WORD * call_data_words + + extend_memory.cost + + init_code_gas, + ) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + contract_address = compute_create2_contract_address( + evm.message.current_target, + salt, + memory_read_bytes(evm.memory, memory_start_position, memory_size), + ) + + generic_create( + evm, + endowment, + contract_address, + memory_start_position, + memory_size, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def return_(evm: Evm) -> None: + """ + Halts execution returning output data. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_position = pop(evm.stack) + memory_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_position, memory_size)] + ) + + charge_gas(evm, GAS_ZERO + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + evm.output = memory_read_bytes( + evm.memory, memory_start_position, memory_size + ) + + evm.running = False + + # PROGRAM COUNTER + pass + + +def generic_call( + evm: Evm, + gas: Uint, + value: U256, + caller: Address, + to: Address, + code_address: Address, + should_transfer_value: bool, + is_staticcall: bool, + memory_input_start_position: U256, + memory_input_size: U256, + memory_output_start_position: U256, + memory_output_size: U256, + code: Bytes, + disable_precompiles: bool, +) -> None: + """ + Perform the core logic of the `CALL*` family of opcodes. + """ + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message + + evm.return_data = b"" + + if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: + evm.gas_left += gas + push(evm.stack, U256(0)) + return + + call_data = memory_read_bytes( + evm.memory, memory_input_start_position, memory_input_size + ) + + child_message = Message( + block_env=evm.message.block_env, + tx_env=evm.message.tx_env, + caller=caller, + target=to, + gas=gas, + value=value, + data=call_data, + code=code, + current_target=to, + depth=evm.message.depth + Uint(1), + code_address=code_address, + should_transfer_value=should_transfer_value, + is_static=True if is_staticcall else evm.message.is_static, + accessed_addresses=evm.accessed_addresses.copy(), + accessed_storage_keys=evm.accessed_storage_keys.copy(), + disable_precompiles=disable_precompiles, + parent_evm=evm, + ) + child_evm = process_message(child_message) + + if child_evm.error: + incorporate_child_on_error(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(0)) + else: + incorporate_child_on_success(evm, child_evm) + evm.return_data = child_evm.output + push(evm.stack, U256(1)) + + actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + memory_write( + evm.memory, + memory_output_start_position, + child_evm.output[:actual_output_size], + ) + + +def call(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + code_address = to + ( + disable_precompiles, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + create_gas_cost = ( + Uint(0) + if is_account_alive(evm.message.block_env.state, to) or value == 0 + else GAS_NEW_ACCOUNT + ) + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + create_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + 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 + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + code_address, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + disable_precompiles, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def callcode(evm: Evm) -> None: + """ + Message-call into this account with alternative account’s code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + value = pop(evm.stack) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + to = evm.message.current_target + + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + ( + disable_precompiles, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + transfer_gas_cost = Uint(0) if value == 0 else GAS_CALL_VALUE + message_call_gas = calculate_message_call_gas( + value, + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost + transfer_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + sender_balance = get_account( + evm.message.block_env.state, evm.message.current_target + ).balance + if sender_balance < value: + push(evm.stack, U256(0)) + evm.return_data = b"" + evm.gas_left += message_call_gas.stipend + else: + generic_call( + evm, + message_call_gas.stipend, + value, + evm.message.current_target, + to, + code_address, + True, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + disable_precompiles, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def selfdestruct(evm: Evm) -> None: + """ + Halt execution and register account for later deletion. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + beneficiary = to_address(pop(evm.stack)) + + # GAS + gas_cost = GAS_SELF_DESTRUCT + if beneficiary not in evm.accessed_addresses: + 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 + ): + gas_cost += GAS_SELF_DESTRUCT_NEW_ACCOUNT + + charge_gas(evm, gas_cost) + if evm.message.is_static: + raise WriteInStaticContext + + originator = evm.message.current_target + originator_balance = get_account( + evm.message.block_env.state, originator + ).balance + + move_ether( + evm.message.block_env.state, + originator, + beneficiary, + originator_balance, + ) + + # register account for deletion only if it was created + # in the same transaction + if originator in evm.message.block_env.state.created_accounts: + # If beneficiary is the same as originator, then + # the ether is burnt. + set_account_balance(evm.message.block_env.state, originator, U256(0)) + evm.accounts_to_delete.add(originator) + + # HALT the execution + evm.running = False + + # PROGRAM COUNTER + pass + + +def delegatecall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + code_address = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if code_address in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(code_address) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + ( + disable_precompiles, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + message_call_gas = calculate_message_call_gas( + U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + evm.message.value, + evm.message.caller, + evm.message.current_target, + code_address, + False, + False, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + disable_precompiles, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def staticcall(evm: Evm) -> None: + """ + Message-call into an account. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + gas = Uint(pop(evm.stack)) + to = to_address(pop(evm.stack)) + memory_input_start_position = pop(evm.stack) + memory_input_size = pop(evm.stack) + memory_output_start_position = pop(evm.stack) + memory_output_size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, + [ + (memory_input_start_position, memory_input_size), + (memory_output_start_position, memory_output_size), + ], + ) + + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + code_address = to + ( + disable_precompiles, + code_address, + code, + delegated_access_gas_cost, + ) = access_delegation(evm, code_address) + access_gas_cost += delegated_access_gas_cost + + message_call_gas = calculate_message_call_gas( + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + access_gas_cost, + ) + charge_gas(evm, message_call_gas.cost + extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + generic_call( + evm, + message_call_gas.stipend, + U256(0), + evm.message.current_target, + to, + code_address, + True, + True, + memory_input_start_position, + memory_input_size, + memory_output_start_position, + memory_output_size, + code, + disable_precompiles, + ) + + # PROGRAM COUNTER + evm.pc += Uint(1) + + +def revert(evm: Evm) -> None: + """ + Stop execution and revert state changes, without consuming all provided gas + and also has the ability to return a reason + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + memory_start_index = pop(evm.stack) + size = pop(evm.stack) + + # GAS + extend_memory = calculate_gas_extend_memory( + evm.memory, [(memory_start_index, size)] + ) + + charge_gas(evm, extend_memory.cost) + + # OPERATION + evm.memory += b"\x00" * extend_memory.expand_by + output = memory_read_bytes(evm.memory, memory_start_index, size) + evm.output = Bytes(output) + raise Revert + + # PROGRAM COUNTER + pass diff --git a/src/ethereum/osaka/vm/interpreter.py b/src/ethereum/osaka/vm/interpreter.py new file mode 100644 index 0000000000..27832ea776 --- /dev/null +++ b/src/ethereum/osaka/vm/interpreter.py @@ -0,0 +1,309 @@ +""" +Ethereum Virtual Machine (EVM) Interpreter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +A straightforward interpreter that executes EVM code. +""" +from dataclasses import dataclass +from typing import Optional, Set, Tuple + +from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.numeric import U256, Uint, ulen + +from ethereum.exceptions import EthereumException +from ethereum.trace import ( + EvmStop, + OpEnd, + OpException, + OpStart, + PrecompileEnd, + PrecompileStart, + TransactionEnd, + evm_trace, +) + +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, + increment_nonce, + mark_account_created, + move_ether, + rollback_transaction, + set_code, +) +from ..vm import Message +from ..vm.eoa_delegation import set_delegation +from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas +from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS +from . import Evm +from .exceptions import ( + AddressCollision, + ExceptionalHalt, + InvalidContractPrefix, + InvalidOpcode, + OutOfGasError, + Revert, + StackDepthLimitError, +) +from .instructions import Ops, op_implementation +from .runtime import get_valid_jump_destinations + +STACK_DEPTH_LIMIT = Uint(1024) +MAX_CODE_SIZE = 0x6000 + + +@dataclass +class MessageCallOutput: + """ + Output of a particular message call + + Contains the following: + + 1. `gas_left`: remaining gas after execution. + 2. `refund_counter`: gas to refund after execution. + 3. `logs`: list of `Log` generated during execution. + 4. `accounts_to_delete`: Contracts which have self-destructed. + 5. `error`: The error from the execution if any. + 6. `return_data`: The output of the execution. + """ + + gas_left: Uint + refund_counter: U256 + logs: Tuple[Log, ...] + accounts_to_delete: Set[Address] + error: Optional[EthereumException] + return_data: Bytes + + +def process_message_call(message: Message) -> MessageCallOutput: + """ + If `message.current` is empty then it creates a smart contract + else it executes a call from the `message.caller` to the `message.target`. + + Parameters + ---------- + message : + Transaction specific items. + + Returns + ------- + output : `MessageCallOutput` + Output of the message call + """ + 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) + if is_collision: + return MessageCallOutput( + Uint(0), + U256(0), + tuple(), + set(), + AddressCollision(), + Bytes(b""), + ) + else: + evm = process_create_message(message) + else: + if message.tx_env.authorizations != (): + refund_counter += set_delegation(message) + evm = process_message(message) + + if evm.error: + logs: Tuple[Log, ...] = () + accounts_to_delete = set() + else: + logs = evm.logs + accounts_to_delete = evm.accounts_to_delete + refund_counter += U256(evm.refund_counter) + + tx_end = TransactionEnd( + int(message.gas) - int(evm.gas_left), evm.output, evm.error + ) + evm_trace(evm, tx_end) + + return MessageCallOutput( + gas_left=evm.gas_left, + refund_counter=refund_counter, + logs=logs, + accounts_to_delete=accounts_to_delete, + error=evm.error, + return_data=evm.output, + ) + + +def process_create_message(message: Message) -> Evm: + """ + Executes a call to create a smart contract. + + Parameters + ---------- + message : + Transaction specific items. + + Returns + ------- + evm: :py:class:`~ethereum.osaka.vm.Evm` + Items containing execution specific objects. + """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage + # take snapshot of state before processing the message + begin_transaction(state, transient_storage) + + # If the address where the account is being created has storage, it is + # destroyed. This can only happen in the following highly unlikely + # circumstances: + # * The address created by a `CREATE` call collides with a subsequent + # `CREATE` or `CREATE2` call. + # * The first `CREATE` happened before Spurious Dragon and left empty + # code. + destroy_storage(state, 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. + mark_account_created(state, message.current_target) + + increment_nonce(state, message.current_target) + evm = process_message(message) + if not evm.error: + contract_code = evm.output + contract_code_gas = Uint(len(contract_code)) * GAS_CODE_DEPOSIT + try: + if len(contract_code) > 0: + if contract_code[0] == 0xEF: + raise InvalidContractPrefix + charge_gas(evm, contract_code_gas) + if len(contract_code) > MAX_CODE_SIZE: + raise OutOfGasError + except ExceptionalHalt as error: + rollback_transaction(state, transient_storage) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + else: + set_code(state, message.current_target, contract_code) + commit_transaction(state, transient_storage) + else: + rollback_transaction(state, transient_storage) + return evm + + +def process_message(message: Message) -> Evm: + """ + Move ether and execute the relevant code. + + Parameters + ---------- + message : + Transaction specific items. + + Returns + ------- + evm: :py:class:`~ethereum.osaka.vm.Evm` + Items containing execution specific objects + """ + state = message.block_env.state + transient_storage = message.tx_env.transient_storage + if message.depth > STACK_DEPTH_LIMIT: + raise StackDepthLimitError("Stack depth limit reached") + + # take snapshot of state before processing the message + begin_transaction(state, transient_storage) + + if message.should_transfer_value and message.value != 0: + move_ether( + state, message.caller, message.current_target, message.value + ) + + evm = execute_code(message) + if evm.error: + # revert state to the last saved checkpoint + # since the message call resulted in an error + rollback_transaction(state, transient_storage) + else: + commit_transaction(state, transient_storage) + return evm + + +def execute_code(message: Message) -> Evm: + """ + Executes bytecode present in the `message`. + + Parameters + ---------- + message : + Transaction specific items. + + Returns + ------- + evm: `ethereum.vm.EVM` + Items containing execution specific objects + """ + code = message.code + valid_jump_destinations = get_valid_jump_destinations(code) + + evm = Evm( + pc=Uint(0), + stack=[], + memory=bytearray(), + code=code, + gas_left=message.gas, + valid_jump_destinations=valid_jump_destinations, + logs=(), + refund_counter=0, + running=True, + message=message, + output=b"", + accounts_to_delete=set(), + return_data=b"", + error=None, + accessed_addresses=message.accessed_addresses, + accessed_storage_keys=message.accessed_storage_keys, + ) + try: + if evm.message.code_address in PRE_COMPILED_CONTRACTS: + if message.disable_precompiles: + return evm + evm_trace(evm, PrecompileStart(evm.message.code_address)) + PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) + evm_trace(evm, PrecompileEnd()) + return evm + + while evm.running and evm.pc < ulen(evm.code): + try: + op = Ops(evm.code[evm.pc]) + except ValueError: + raise InvalidOpcode(evm.code[evm.pc]) + + evm_trace(evm, OpStart(op)) + op_implementation[op](evm) + evm_trace(evm, OpEnd()) + + evm_trace(evm, EvmStop(Ops.STOP)) + + except ExceptionalHalt as error: + evm_trace(evm, OpException(error)) + evm.gas_left = Uint(0) + evm.output = b"" + evm.error = error + except Revert as error: + evm_trace(evm, OpException(error)) + evm.error = error + return evm diff --git a/src/ethereum/osaka/vm/memory.py b/src/ethereum/osaka/vm/memory.py new file mode 100644 index 0000000000..aa2e7fdd57 --- /dev/null +++ b/src/ethereum/osaka/vm/memory.py @@ -0,0 +1,81 @@ +""" +Ethereum Virtual Machine (EVM) Memory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +EVM memory operations. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ethereum.utils.byte import right_pad_zero_bytes + + +def memory_write( + memory: bytearray, start_position: U256, value: Bytes +) -> None: + """ + Writes to memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + value : + Data to write to memory. + """ + memory[start_position : int(start_position) + len(value)] = value + + +def memory_read_bytes( + memory: bytearray, start_position: U256, size: U256 +) -> bytearray: + """ + Read bytes from memory. + + Parameters + ---------- + memory : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return memory[start_position : Uint(start_position) + Uint(size)] + + +def buffer_read(buffer: Bytes, start_position: U256, size: U256) -> Bytes: + """ + Read bytes from a buffer. Padding with zeros if necessary. + + Parameters + ---------- + buffer : + Memory contents of the EVM. + start_position : + Starting pointer to the memory. + size : + Size of the data that needs to be read from `start_position`. + + Returns + ------- + data_bytes : + Data read from memory. + """ + return right_pad_zero_bytes( + buffer[start_position : Uint(start_position) + Uint(size)], size + ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/__init__.py new file mode 100644 index 0000000000..8ab92bb0f3 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/__init__.py @@ -0,0 +1,54 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Addresses of precompiled contracts and mappings to their +implementations. +""" + +from ...utils.hexadecimal import hex_to_address + +__all__ = ( + "ECRECOVER_ADDRESS", + "SHA256_ADDRESS", + "RIPEMD160_ADDRESS", + "IDENTITY_ADDRESS", + "MODEXP_ADDRESS", + "ALT_BN128_ADD_ADDRESS", + "ALT_BN128_MUL_ADDRESS", + "ALT_BN128_PAIRING_CHECK_ADDRESS", + "BLAKE2F_ADDRESS", + "POINT_EVALUATION_ADDRESS", + "BLS12_G1_ADD_ADDRESS", + "BLS12_G1_MSM_ADDRESS", + "BLS12_G2_ADD_ADDRESS", + "BLS12_G2_MSM_ADDRESS", + "BLS12_PAIRING_ADDRESS", + "BLS12_MAP_FP_TO_G1_ADDRESS", + "BLS12_MAP_FP2_TO_G2_ADDRESS", +) + +ECRECOVER_ADDRESS = hex_to_address("0x01") +SHA256_ADDRESS = hex_to_address("0x02") +RIPEMD160_ADDRESS = hex_to_address("0x03") +IDENTITY_ADDRESS = hex_to_address("0x04") +MODEXP_ADDRESS = hex_to_address("0x05") +ALT_BN128_ADD_ADDRESS = hex_to_address("0x06") +ALT_BN128_MUL_ADDRESS = hex_to_address("0x07") +ALT_BN128_PAIRING_CHECK_ADDRESS = hex_to_address("0x08") +BLAKE2F_ADDRESS = hex_to_address("0x09") +POINT_EVALUATION_ADDRESS = hex_to_address("0x0a") +BLS12_G1_ADD_ADDRESS = hex_to_address("0x0b") +BLS12_G1_MSM_ADDRESS = hex_to_address("0x0c") +BLS12_G2_ADD_ADDRESS = hex_to_address("0x0d") +BLS12_G2_MSM_ADDRESS = hex_to_address("0x0e") +BLS12_PAIRING_ADDRESS = hex_to_address("0x0f") +BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10") +BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11") diff --git a/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py new file mode 100644 index 0000000000..78490cf2c5 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/alt_bn128.py @@ -0,0 +1,227 @@ +""" +Ethereum Virtual Machine (EVM) ALT_BN128 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ALT_BN128 precompiled contracts. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint +from py_ecc.bn128.bn128_curve import ( + FQ, + FQ2, + FQ12, + add, + b, + b2, + curve_order, + field_modulus, + is_on_curve, + multiply, +) +from py_ecc.bn128.bn128_pairing import pairing +from py_ecc.typing import Point2D + +from ...vm import Evm +from ...vm.gas import charge_gas +from ...vm.memory import buffer_read +from ..exceptions import InvalidParameter, OutOfGasError + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 64 bytes to a point on the curve. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 64: + raise InvalidParameter("Input should be 64 bytes long") + + x_bytes = buffer_read(data, U256(0), U256(32)) + x = int(U256.from_be_bytes(x_bytes)) + y_bytes = buffer_read(data, U256(32), U256(32)) + y = int(U256.from_be_bytes(y_bytes)) + + if x >= field_modulus: + raise InvalidParameter("Invalid field element") + if y >= field_modulus: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G2 point. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + A point on the curve. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("G2 should be 128 bytes long") + + x0_bytes = buffer_read(data, U256(0), U256(32)) + x0 = int(U256.from_be_bytes(x0_bytes)) + x1_bytes = buffer_read(data, U256(32), U256(32)) + x1 = int(U256.from_be_bytes(x1_bytes)) + + y0_bytes = buffer_read(data, U256(64), U256(32)) + y0 = int(U256.from_be_bytes(y0_bytes)) + y1_bytes = buffer_read(data, U256(96), U256(32)) + y1 = int(U256.from_be_bytes(y1_bytes)) + + if x0 >= field_modulus or x1 >= field_modulus: + raise InvalidParameter("Invalid field element") + if y0 >= field_modulus or y1 >= field_modulus: + raise InvalidParameter("Invalid field element") + + x = FQ2((x1, x0)) + y = FQ2((y1, y0)) + + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point + + +def alt_bn128_add(evm: Evm) -> None: + """ + The ALT_BN128 addition precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(150)) + + # OPERATION + try: + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + p1 = bytes_to_G1(buffer_read(data, U256(64), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + + p = add(p0, p1) + if p is None: + x, y = (0, 0) + else: + x, y = p + + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() + + +def alt_bn128_mul(evm: Evm) -> None: + """ + The ALT_BN128 multiplication precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(6000)) + + # OPERATION + try: + p0 = bytes_to_G1(buffer_read(data, U256(0), U256(64))) + except InvalidParameter as e: + raise OutOfGasError from e + n = int(U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))) + + p = multiply(p0, n) + if p is None: + x, y = (0, 0) + else: + x, y = p + + evm.output = Uint(x).to_be_bytes32() + Uint(y).to_be_bytes32() + + +def alt_bn128_pairing_check(evm: Evm) -> None: + """ + The ALT_BN128 pairing check precompiled contract. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, Uint(34000 * (len(data) // 192) + 45000)) + + # OPERATION + if len(data) % 192 != 0: + raise OutOfGasError + result = FQ12.one() + for i in range(len(data) // 192): + try: + p = bytes_to_G1(buffer_read(data, U256(192 * i), U256(64))) + q = bytes_to_G2(buffer_read(data, U256(192 * i + 64), U256(128))) + except InvalidParameter as e: + raise OutOfGasError from e + if multiply(p, curve_order) is not None: + raise OutOfGasError + if multiply(q, curve_order) is not None: + raise OutOfGasError + if p is not None and q is not None: + result *= pairing(q, p) + + if result == FQ12.one(): + evm.output = U256(1).to_be_bytes32() + else: + evm.output = U256(0).to_be_bytes32() diff --git a/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py b/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py new file mode 100644 index 0000000000..0d86ba6e85 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/blake2f.py @@ -0,0 +1,41 @@ +""" +Ethereum Virtual Machine (EVM) Blake2 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `Blake2` precompiled contract. +""" +from ethereum.crypto.blake2 import Blake2b + +from ...vm import Evm +from ...vm.gas import GAS_BLAKE2_PER_ROUND, charge_gas +from ..exceptions import InvalidParameter + + +def blake2f(evm: Evm) -> None: + """ + Writes the Blake2 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + if len(data) != 213: + raise InvalidParameter + + blake2b = Blake2b() + rounds, h, m, t_0, t_1, f = blake2b.get_blake2_parameters(data) + + charge_gas(evm, GAS_BLAKE2_PER_ROUND * rounds) + if f not in [0, 1]: + raise InvalidParameter + + evm.output = blake2b.compress(rounds, h, m, t_0, t_1, f) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py new file mode 100644 index 0000000000..2126a6ab39 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/__init__.py @@ -0,0 +1,583 @@ +""" +BLS12 381 Precompile +^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Precompile for BLS12-381 curve operations. +""" +from typing import Tuple, Union + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import ( + FQ, + FQ2, + b, + b2, + curve_order, + is_on_curve, + multiply, +) +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.typing import Point2D + +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter + +P = FQ.field_modulus + +G1_K_DISCOUNT = [ + 1000, + 949, + 848, + 797, + 764, + 750, + 738, + 728, + 719, + 712, + 705, + 698, + 692, + 687, + 682, + 677, + 673, + 669, + 665, + 661, + 658, + 654, + 651, + 648, + 645, + 642, + 640, + 637, + 635, + 632, + 630, + 627, + 625, + 623, + 621, + 619, + 617, + 615, + 613, + 611, + 609, + 608, + 606, + 604, + 603, + 601, + 599, + 598, + 596, + 595, + 593, + 592, + 591, + 589, + 588, + 586, + 585, + 584, + 582, + 581, + 580, + 579, + 577, + 576, + 575, + 574, + 573, + 572, + 570, + 569, + 568, + 567, + 566, + 565, + 564, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 551, + 550, + 549, + 548, + 547, + 547, + 546, + 545, + 544, + 543, + 542, + 541, + 540, + 540, + 539, + 538, + 537, + 536, + 536, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 529, + 528, + 528, + 527, + 526, + 525, + 525, + 524, + 523, + 522, + 522, + 521, + 520, + 520, + 519, +] + +G2_K_DISCOUNT = [ + 1000, + 1000, + 923, + 884, + 855, + 832, + 812, + 796, + 782, + 770, + 759, + 749, + 740, + 732, + 724, + 717, + 711, + 704, + 699, + 693, + 688, + 683, + 679, + 674, + 670, + 666, + 663, + 659, + 655, + 652, + 649, + 646, + 643, + 640, + 637, + 634, + 632, + 629, + 627, + 624, + 622, + 620, + 618, + 615, + 613, + 611, + 609, + 607, + 606, + 604, + 602, + 600, + 598, + 597, + 595, + 593, + 592, + 590, + 589, + 587, + 586, + 584, + 583, + 582, + 580, + 579, + 578, + 576, + 575, + 574, + 573, + 571, + 570, + 569, + 568, + 567, + 566, + 565, + 563, + 562, + 561, + 560, + 559, + 558, + 557, + 556, + 555, + 554, + 553, + 552, + 552, + 551, + 550, + 549, + 548, + 547, + 546, + 545, + 545, + 544, + 543, + 542, + 541, + 541, + 540, + 539, + 538, + 537, + 537, + 536, + 535, + 535, + 534, + 533, + 532, + 532, + 531, + 530, + 530, + 529, + 528, + 528, + 527, + 526, + 526, + 525, + 524, + 524, +] + +G1_MAX_DISCOUNT = 519 +G2_MAX_DISCOUNT = 524 +MULTIPLIER = Uint(1000) + + +def bytes_to_G1(data: Bytes) -> Point2D: + """ + Decode 128 bytes to a G1 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G1 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 128: + raise InvalidParameter("Input should be 128 bytes long") + + x = int.from_bytes(data[:64], "big") + y = int.from_bytes(data[64:], "big") + + if x >= P: + raise InvalidParameter("Invalid field element") + if y >= P: + raise InvalidParameter("Invalid field element") + + if x == 0 and y == 0: + return None + + point = (FQ(x), FQ(y)) + + # Check if the point is on the curve + if not is_on_curve(point, b): + raise InvalidParameter("Point is not on curve") + + return point + + +def G1_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G1 point to 128 bytes. + + Parameters + ---------- + point : + The G1 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 128 + + x, y = point + + x_bytes = int(x).to_bytes(64, "big") + y_bytes = int(y).to_bytes(64, "big") + + return x_bytes + y_bytes + + +def decode_G1_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 160 bytes to a G1 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G1 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 160: + InvalidParameter("Input should be 160 bytes long") + + p = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(128), U256(32)), "big") + + return p, m + + +def bytes_to_FQ( + data: Bytes, optimized: bool = False +) -> Union[FQ, OPTIMIZED_FQ]: + """ + Decode 64 bytes to a FQ element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ implementation. + + Returns + ------- + fq : Union[FQ, OPTIMIZED_FQ] + The FQ element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 64: + raise InvalidParameter("FQ should be 64 bytes long") + + c = int.from_bytes(data[:64], "big") + + if c >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ(c) + else: + return FQ(c) + + +def bytes_to_FQ2( + data: Bytes, optimized: bool = False +) -> Union[FQ2, OPTIMIZED_FQ2]: + """ + Decode 128 bytes to a FQ2 element. + + Parameters + ---------- + data : + The bytes data to decode. + optimized : + Whether to use the optimized FQ2 implementation. + + Returns + ------- + fq2 : Union[FQ2, OPTIMIZED_FQ2] + The FQ2 element. + + Raises + ------ + InvalidParameter + If the field element is invalid. + """ + if len(data) != 128: + raise InvalidParameter("FQ2 input should be 128 bytes long") + c_0 = int.from_bytes(data[:64], "big") + c_1 = int.from_bytes(data[64:], "big") + + if c_0 >= P: + raise InvalidParameter("Invalid field element") + if c_1 >= P: + raise InvalidParameter("Invalid field element") + + if optimized: + return OPTIMIZED_FQ2((c_0, c_1)) + else: + return FQ2((c_0, c_1)) + + +def bytes_to_G2(data: Bytes) -> Point2D: + """ + Decode 256 bytes to a G2 point. Does not perform sub-group check. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Point2D + The G2 point. + + Raises + ------ + InvalidParameter + Either a field element is invalid or the point is not on the curve. + """ + if len(data) != 256: + raise InvalidParameter("G2 should be 256 bytes long") + + x = bytes_to_FQ2(data[:128]) + y = bytes_to_FQ2(data[128:]) + + assert isinstance(x, FQ2) and isinstance(y, FQ2) + if x == FQ2((0, 0)) and y == FQ2((0, 0)): + return None + + point = (x, y) + + # Check if the point is on the curve + if not is_on_curve(point, b2): + raise InvalidParameter("Point is not on curve") + + return point + + +def FQ2_to_bytes(fq2: FQ2) -> Bytes: + """ + Encode a FQ2 point to 128 bytes. + + Parameters + ---------- + fq2 : + The FQ2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + c_0, c_1 = fq2.coeffs + return int(c_0).to_bytes(64, "big") + int(c_1).to_bytes(64, "big") + + +def G2_to_bytes(point: Point2D) -> Bytes: + """ + Encode a G2 point to 256 bytes. + + Parameters + ---------- + point : + The G2 point to encode. + + Returns + ------- + data : Bytes + The encoded data. + """ + if point is None: + return b"\x00" * 256 + + x, y = point + + return FQ2_to_bytes(x) + FQ2_to_bytes(y) + + +def decode_G2_scalar_pair(data: Bytes) -> Tuple[Point2D, int]: + """ + Decode 288 bytes to a G2 point and a scalar. + + Parameters + ---------- + data : + The bytes data to decode. + + Returns + ------- + point : Tuple[Point2D, int] + The G2 point and the scalar. + + Raises + ------ + InvalidParameter + If the sub-group check failed. + """ + if len(data) != 288: + InvalidParameter("Input should be 288 bytes long") + + p = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + if multiply(p, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + m = int.from_bytes(buffer_read(data, U256(256), U256(32)), "big") + + return p, m diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py new file mode 100644 index 0000000000..541395de40 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g1.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G1 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G1, map_to_curve_G1 +from py_ecc.optimized_bls12_381.optimized_curve import FQ as OPTIMIZED_FQ +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G1_ADD, + GAS_BLS_G1_MAP, + GAS_BLS_G1_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G1_K_DISCOUNT, + G1_MAX_DISCOUNT, + MULTIPLIER, + G1_to_bytes, + bytes_to_FQ, + bytes_to_G1, + decode_G1_scalar_pair, +) + +LENGTH_PER_PAIR = 160 + + +def bls12_g1_add(evm: Evm) -> None: + """ + The bls12_381 G1 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 256: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_ADD)) + + # OPERATION + p1 = bytes_to_G1(buffer_read(data, U256(0), U256(128))) + p2 = bytes_to_G1(buffer_read(data, U256(128), U256(128))) + + result = add(p1, p2) + + evm.output = G1_to_bytes(result) + + +def bls12_g1_msm(evm: Evm) -> None: + """ + The bls12_381 G1 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G1_K_DISCOUNT[k - 1]) + else: + discount = Uint(G1_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G1_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G1_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G1_to_bytes(result) + + +def bls12_map_fp_to_g1(evm: Evm) -> None: + """ + Precompile to map field element to G1. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 64: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G1_MAP)) + + # OPERATION + field_element = bytes_to_FQ(data, True) + assert isinstance(field_element, OPTIMIZED_FQ) + + g1_uncompressed = clear_cofactor_G1(map_to_curve_G1(field_element)) + g1_normalised = normalize(g1_uncompressed) + + evm.output = G1_to_bytes(g1_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py new file mode 100644 index 0000000000..bda7f3641f --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_g2.py @@ -0,0 +1,148 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 G2 CONTRACTS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of pre-compiles in G2 (curve over base prime field). +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import add, multiply +from py_ecc.bls.hash_to_curve import clear_cofactor_G2, map_to_curve_G2 +from py_ecc.optimized_bls12_381.optimized_curve import FQ2 as OPTIMIZED_FQ2 +from py_ecc.optimized_bls12_381.optimized_curve import normalize + +from ....vm import Evm +from ....vm.gas import ( + GAS_BLS_G2_ADD, + GAS_BLS_G2_MAP, + GAS_BLS_G2_MUL, + charge_gas, +) +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import ( + G2_K_DISCOUNT, + G2_MAX_DISCOUNT, + MULTIPLIER, + G2_to_bytes, + bytes_to_FQ2, + bytes_to_G2, + decode_G2_scalar_pair, +) + +LENGTH_PER_PAIR = 288 + + +def bls12_g2_add(evm: Evm) -> None: + """ + The bls12_381 G2 point addition precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 512: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_ADD)) + + # OPERATION + p1 = bytes_to_G2(buffer_read(data, U256(0), U256(256))) + p2 = bytes_to_G2(buffer_read(data, U256(256), U256(256))) + + result = add(p1, p2) + + evm.output = G2_to_bytes(result) + + +def bls12_g2_msm(evm: Evm) -> None: + """ + The bls12_381 G2 multi-scalar multiplication precompile. + Note: This uses the naive approach to multi-scalar multiplication + which is not suitably optimized for production clients. Clients are + required to implement a more efficient algorithm such as the Pippenger + algorithm. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // LENGTH_PER_PAIR + if k <= 128: + discount = Uint(G2_K_DISCOUNT[k - 1]) + else: + discount = Uint(G2_MAX_DISCOUNT) + + gas_cost = Uint(k) * GAS_BLS_G2_MUL * discount // MULTIPLIER + charge_gas(evm, gas_cost) + + # OPERATION + for i in range(k): + start_index = i * LENGTH_PER_PAIR + end_index = start_index + LENGTH_PER_PAIR + + p, m = decode_G2_scalar_pair(data[start_index:end_index]) + product = multiply(p, m) + + if i == 0: + result = product + else: + result = add(result, product) + + evm.output = G2_to_bytes(result) + + +def bls12_map_fp2_to_g2(evm: Evm) -> None: + """ + Precompile to map field element to G2. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid. + """ + data = evm.message.data + if len(data) != 128: + raise InvalidParameter("Invalid Input Length") + + # GAS + charge_gas(evm, Uint(GAS_BLS_G2_MAP)) + + # OPERATION + field_element = bytes_to_FQ2(data, True) + assert isinstance(field_element, OPTIMIZED_FQ2) + + g2_uncompressed = clear_cofactor_G2(map_to_curve_G2(field_element)) + g2_normalised = normalize(g2_uncompressed) + + evm.output = G2_to_bytes(g2_normalised) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py new file mode 100644 index 0000000000..2a03a6897a --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py @@ -0,0 +1,67 @@ +""" +Ethereum Virtual Machine (EVM) BLS12 381 PAIRING PRE-COMPILE +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the BLS12 381 pairing pre-compile. +""" +from ethereum_types.numeric import U256, Uint +from py_ecc.bls12_381.bls12_381_curve import FQ12, curve_order, multiply +from py_ecc.bls12_381.bls12_381_pairing import pairing + +from ....vm import Evm +from ....vm.gas import charge_gas +from ....vm.memory import buffer_read +from ...exceptions import InvalidParameter +from . import bytes_to_G1, bytes_to_G2 + + +def bls12_pairing(evm: Evm) -> None: + """ + The bls12_381 pairing precompile. + + Parameters + ---------- + evm : + The current EVM frame. + + Raises + ------ + InvalidParameter + If the input length is invalid or if sub-group check fails. + """ + data = evm.message.data + if len(data) == 0 or len(data) % 384 != 0: + raise InvalidParameter("Invalid Input Length") + + # GAS + k = len(data) // 384 + gas_cost = Uint(32600 * k + 37700) + charge_gas(evm, gas_cost) + + # OPERATION + result = FQ12.one() + for i in range(k): + g1_start = Uint(384 * i) + g2_start = Uint(384 * i + 128) + + g1_point = bytes_to_G1(buffer_read(data, U256(g1_start), U256(128))) + if multiply(g1_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + g2_point = bytes_to_G2(buffer_read(data, U256(g2_start), U256(256))) + if multiply(g2_point, curve_order) is not None: + raise InvalidParameter("Sub-group check failed.") + + result *= pairing(g2_point, g1_point) + + if result == FQ12.one(): + evm.output = b"\x00" * 31 + b"\x01" + else: + evm.output = b"\x00" * 32 diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py b/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py new file mode 100644 index 0000000000..1f047d3a44 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/ecrecover.py @@ -0,0 +1,63 @@ +""" +Ethereum Virtual Machine (EVM) ECRECOVER PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the ECRECOVER precompiled contract. +""" +from ethereum_types.numeric import U256 + +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError +from ethereum.utils.byte import left_pad_zero_bytes + +from ...vm import Evm +from ...vm.gas import GAS_ECRECOVER, charge_gas +from ...vm.memory import buffer_read + + +def ecrecover(evm: Evm) -> None: + """ + Decrypts the address using elliptic curve DSA recovery mechanism and writes + the address to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + charge_gas(evm, GAS_ECRECOVER) + + # OPERATION + message_hash_bytes = buffer_read(data, U256(0), U256(32)) + message_hash = Hash32(message_hash_bytes) + v = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + r = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + s = U256.from_be_bytes(buffer_read(data, U256(96), U256(32))) + + if v != U256(27) and v != U256(28): + return + if U256(0) >= r or r >= SECP256K1N: + return + if U256(0) >= s or s >= SECP256K1N: + return + + try: + public_key = secp256k1_recover(r, s, v - U256(27), message_hash) + except InvalidSignatureError: + # unable to extract public key + return + + address = keccak256(public_key)[12:32] + padded_address = left_pad_zero_bytes(address, 32) + evm.output = padded_address diff --git a/src/ethereum/osaka/vm/precompiled_contracts/identity.py b/src/ethereum/osaka/vm/precompiled_contracts/identity.py new file mode 100644 index 0000000000..88729c96d7 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/identity.py @@ -0,0 +1,38 @@ +""" +Ethereum Virtual Machine (EVM) IDENTITY PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `IDENTITY` precompiled contract. +""" +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_IDENTITY, GAS_IDENTITY_WORD, charge_gas + + +def identity(evm: Evm) -> None: + """ + Writes the message data to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_IDENTITY + GAS_IDENTITY_WORD * word_count) + + # OPERATION + evm.output = data diff --git a/src/ethereum/osaka/vm/precompiled_contracts/mapping.py b/src/ethereum/osaka/vm/precompiled_contracts/mapping.py new file mode 100644 index 0000000000..3094d8dff2 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/mapping.py @@ -0,0 +1,74 @@ +""" +Precompiled Contract Addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Mapping of precompiled contracts their implementations. +""" +from typing import Callable, Dict + +from ...fork_types import Address +from . import ( + ALT_BN128_ADD_ADDRESS, + ALT_BN128_MUL_ADDRESS, + ALT_BN128_PAIRING_CHECK_ADDRESS, + BLAKE2F_ADDRESS, + BLS12_G1_ADD_ADDRESS, + BLS12_G1_MSM_ADDRESS, + BLS12_G2_ADD_ADDRESS, + BLS12_G2_MSM_ADDRESS, + BLS12_MAP_FP2_TO_G2_ADDRESS, + BLS12_MAP_FP_TO_G1_ADDRESS, + BLS12_PAIRING_ADDRESS, + ECRECOVER_ADDRESS, + IDENTITY_ADDRESS, + MODEXP_ADDRESS, + POINT_EVALUATION_ADDRESS, + RIPEMD160_ADDRESS, + SHA256_ADDRESS, +) +from .alt_bn128 import alt_bn128_add, alt_bn128_mul, alt_bn128_pairing_check +from .blake2f import blake2f +from .bls12_381.bls12_381_g1 import ( + bls12_g1_add, + bls12_g1_msm, + bls12_map_fp_to_g1, +) +from .bls12_381.bls12_381_g2 import ( + bls12_g2_add, + bls12_g2_msm, + bls12_map_fp2_to_g2, +) +from .bls12_381.bls12_381_pairing import bls12_pairing +from .ecrecover import ecrecover +from .identity import identity +from .modexp import modexp +from .point_evaluation import point_evaluation +from .ripemd160 import ripemd160 +from .sha256 import sha256 + +PRE_COMPILED_CONTRACTS: Dict[Address, Callable] = { + ECRECOVER_ADDRESS: ecrecover, + SHA256_ADDRESS: sha256, + RIPEMD160_ADDRESS: ripemd160, + IDENTITY_ADDRESS: identity, + MODEXP_ADDRESS: modexp, + ALT_BN128_ADD_ADDRESS: alt_bn128_add, + ALT_BN128_MUL_ADDRESS: alt_bn128_mul, + ALT_BN128_PAIRING_CHECK_ADDRESS: alt_bn128_pairing_check, + BLAKE2F_ADDRESS: blake2f, + POINT_EVALUATION_ADDRESS: point_evaluation, + BLS12_G1_ADD_ADDRESS: bls12_g1_add, + BLS12_G1_MSM_ADDRESS: bls12_g1_msm, + BLS12_G2_ADD_ADDRESS: bls12_g2_add, + BLS12_G2_MSM_ADDRESS: bls12_g2_msm, + BLS12_PAIRING_ADDRESS: bls12_pairing, + BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1, + BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2, +} diff --git a/src/ethereum/osaka/vm/precompiled_contracts/modexp.py b/src/ethereum/osaka/vm/precompiled_contracts/modexp.py new file mode 100644 index 0000000000..403fe86b11 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/modexp.py @@ -0,0 +1,169 @@ +""" +Ethereum Virtual Machine (EVM) MODEXP PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `MODEXP` precompiled contract. +""" +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U256, Uint + +from ...vm import Evm +from ...vm.gas import charge_gas +from ..memory import buffer_read + +GQUADDIVISOR = Uint(3) + + +def modexp(evm: Evm) -> None: + """ + Calculates `(base**exp) % modulus` for arbitrary sized `base`, `exp` and. + `modulus`. The return value is the same length as the modulus. + """ + data = evm.message.data + + # GAS + base_length = U256.from_be_bytes(buffer_read(data, U256(0), U256(32))) + exp_length = U256.from_be_bytes(buffer_read(data, U256(32), U256(32))) + modulus_length = U256.from_be_bytes(buffer_read(data, U256(64), U256(32))) + + exp_start = U256(96) + base_length + + exp_head = Uint.from_be_bytes( + buffer_read(data, exp_start, min(U256(32), exp_length)) + ) + + charge_gas( + evm, + gas_cost(base_length, modulus_length, exp_length, exp_head), + ) + + # OPERATION + if base_length == 0 and modulus_length == 0: + evm.output = Bytes() + return + + base = Uint.from_be_bytes(buffer_read(data, U256(96), base_length)) + exp = Uint.from_be_bytes(buffer_read(data, exp_start, exp_length)) + + modulus_start = exp_start + exp_length + modulus = Uint.from_be_bytes( + buffer_read(data, modulus_start, modulus_length) + ) + + if modulus == 0: + evm.output = Bytes(b"\x00") * modulus_length + else: + evm.output = pow(base, exp, modulus).to_bytes( + Uint(modulus_length), "big" + ) + + +def complexity(base_length: U256, modulus_length: U256) -> Uint: + """ + Estimate the complexity of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + Returns + ------- + + complexity : `Uint` + Complexity of performing the operation. + """ + max_length = max(Uint(base_length), Uint(modulus_length)) + words = (max_length + Uint(7)) // Uint(8) + return words ** Uint(2) + + +def iterations(exponent_length: U256, exponent_head: Uint) -> Uint: + """ + Calculate the number of iterations required to perform a modular + exponentiation. + + Parameters + ---------- + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + iterations : `Uint` + Number of iterations. + """ + if exponent_length <= U256(32) and exponent_head == U256(0): + count = Uint(0) + elif exponent_length <= U256(32): + bit_length = Uint(exponent_head.bit_length()) + + if bit_length > Uint(0): + bit_length -= Uint(1) + + count = bit_length + else: + length_part = Uint(8) * (Uint(exponent_length) - Uint(32)) + bits_part = Uint(exponent_head.bit_length()) + + if bits_part > Uint(0): + bits_part -= Uint(1) + + count = length_part + bits_part + + return max(count, Uint(1)) + + +def gas_cost( + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent_head: Uint, +) -> Uint: + """ + Calculate the gas cost of performing a modular exponentiation. + + Parameters + ---------- + + base_length : + Length of the array representing the base integer. + + modulus_length : + Length of the array representing the modulus integer. + + exponent_length : + Length of the array representing the exponent integer. + + exponent_head : + First 32 bytes of the exponent (with leading zero padding if it is + shorter than 32 bytes), as an unsigned integer. + + Returns + ------- + + gas_cost : `Uint` + Gas required for performing the operation. + """ + multiplication_complexity = complexity(base_length, modulus_length) + iteration_count = iterations(exponent_length, exponent_head) + cost = multiplication_complexity * iteration_count + cost //= GQUADDIVISOR + return max(Uint(200), cost) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py b/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py new file mode 100644 index 0000000000..188f90f83f --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/point_evaluation.py @@ -0,0 +1,72 @@ +""" +Ethereum Virtual Machine (EVM) POINT EVALUATION PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the POINT EVALUATION precompiled contract. +""" +from ethereum_types.bytes import Bytes, Bytes32, Bytes48 +from ethereum_types.numeric import U256 + +from ethereum.crypto.kzg import ( + KZGCommitment, + kzg_commitment_to_versioned_hash, + verify_kzg_proof, +) + +from ...vm import Evm +from ...vm.exceptions import KZGProofError +from ...vm.gas import GAS_POINT_EVALUATION, charge_gas + +FIELD_ELEMENTS_PER_BLOB = 4096 +BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513 # noqa: E501 +VERSIONED_HASH_VERSION_KZG = b"\x01" + + +def point_evaluation(evm: Evm) -> None: + """ + A pre-compile that verifies a KZG proof which claims that a blob + (represented by a commitment) evaluates to a given value at a given point. + + Parameters + ---------- + evm : + The current EVM frame. + + """ + data = evm.message.data + if len(data) != 192: + raise KZGProofError + + versioned_hash = data[:32] + z = Bytes32(data[32:64]) + y = Bytes32(data[64:96]) + commitment = KZGCommitment(data[96:144]) + proof = Bytes48(data[144:192]) + + # GAS + charge_gas(evm, GAS_POINT_EVALUATION) + if kzg_commitment_to_versioned_hash(commitment) != versioned_hash: + raise KZGProofError + + # Verify KZG proof with z and y in big endian format + try: + kzg_proof_verification = verify_kzg_proof(commitment, z, y, proof) + except Exception as e: + raise KZGProofError from e + + if not kzg_proof_verification: + raise KZGProofError + + # Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded + # 32 byte big endian values + evm.output = Bytes( + U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + + U256(BLS_MODULUS).to_be_bytes32() + ) diff --git a/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py b/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py new file mode 100644 index 0000000000..6af1086a82 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/ripemd160.py @@ -0,0 +1,43 @@ +""" +Ethereum Virtual Machine (EVM) RIPEMD160 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `RIPEMD160` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.byte import left_pad_zero_bytes +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_RIPEMD160, GAS_RIPEMD160_WORD, charge_gas + + +def ripemd160(evm: Evm) -> None: + """ + Writes the ripemd160 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_RIPEMD160 + GAS_RIPEMD160_WORD * word_count) + + # OPERATION + hash_bytes = hashlib.new("ripemd160", data).digest() + padded_hash = left_pad_zero_bytes(hash_bytes, 32) + evm.output = padded_hash diff --git a/src/ethereum/osaka/vm/precompiled_contracts/sha256.py b/src/ethereum/osaka/vm/precompiled_contracts/sha256.py new file mode 100644 index 0000000000..db33a37967 --- /dev/null +++ b/src/ethereum/osaka/vm/precompiled_contracts/sha256.py @@ -0,0 +1,40 @@ +""" +Ethereum Virtual Machine (EVM) SHA256 PRECOMPILED CONTRACT +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the `SHA256` precompiled contract. +""" +import hashlib + +from ethereum_types.numeric import Uint + +from ethereum.utils.numeric import ceil32 + +from ...vm import Evm +from ...vm.gas import GAS_SHA256, GAS_SHA256_WORD, charge_gas + + +def sha256(evm: Evm) -> None: + """ + Writes the sha256 hash to output. + + Parameters + ---------- + evm : + The current EVM frame. + """ + data = evm.message.data + + # GAS + word_count = ceil32(Uint(len(data))) // Uint(32) + charge_gas(evm, GAS_SHA256 + GAS_SHA256_WORD * word_count) + + # OPERATION + evm.output = hashlib.sha256(data).digest() diff --git a/src/ethereum/osaka/vm/runtime.py b/src/ethereum/osaka/vm/runtime.py new file mode 100644 index 0000000000..acead2be90 --- /dev/null +++ b/src/ethereum/osaka/vm/runtime.py @@ -0,0 +1,68 @@ +""" +Ethereum Virtual Machine (EVM) Runtime Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Runtime related operations used while executing EVM code. +""" +from typing import Set + +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import Uint, ulen + +from .instructions import Ops + + +def get_valid_jump_destinations(code: Bytes) -> Set[Uint]: + """ + Analyze the evm code to obtain the set of valid jump destinations. + + Valid jump destinations are defined as follows: + * The jump destination is less than the length of the code. + * The jump destination should have the `JUMPDEST` opcode (0x5B). + * The jump destination shouldn't be part of the data corresponding to + `PUSH-N` opcodes. + + Note - Jump destinations are 0-indexed. + + Parameters + ---------- + code : + The EVM code which is to be executed. + + Returns + ------- + valid_jump_destinations: `Set[Uint]` + The set of valid jump destinations in the code. + """ + valid_jump_destinations = set() + pc = Uint(0) + + while pc < ulen(code): + try: + current_opcode = Ops(code[pc]) + except ValueError: + # Skip invalid opcodes, as they don't affect the jumpdest + # analysis. Nevertheless, such invalid opcodes would be caught + # and raised when the interpreter runs. + pc += Uint(1) + continue + + if current_opcode == Ops.JUMPDEST: + valid_jump_destinations.add(pc) + elif Ops.PUSH1.value <= current_opcode.value <= Ops.PUSH32.value: + # If PUSH-N opcodes are encountered, skip the current opcode along + # with the trailing data segment corresponding to the PUSH-N + # opcodes. + push_data_size = current_opcode.value - Ops.PUSH1.value + 1 + pc += Uint(push_data_size) + + pc += Uint(1) + + return valid_jump_destinations diff --git a/src/ethereum/osaka/vm/stack.py b/src/ethereum/osaka/vm/stack.py new file mode 100644 index 0000000000..f28a5b3b88 --- /dev/null +++ b/src/ethereum/osaka/vm/stack.py @@ -0,0 +1,59 @@ +""" +Ethereum Virtual Machine (EVM) Stack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. contents:: Table of Contents + :backlinks: none + :local: + +Introduction +------------ + +Implementation of the stack operators for the EVM. +""" + +from typing import List + +from ethereum_types.numeric import U256 + +from .exceptions import StackOverflowError, StackUnderflowError + + +def pop(stack: List[U256]) -> U256: + """ + Pops the top item off of `stack`. + + Parameters + ---------- + stack : + EVM stack. + + Returns + ------- + value : `U256` + The top element on the stack. + + """ + if len(stack) == 0: + raise StackUnderflowError + + return stack.pop() + + +def push(stack: List[U256], value: U256) -> None: + """ + Pushes `value` onto `stack`. + + Parameters + ---------- + stack : + EVM stack. + + value : + Item to be pushed onto `stack`. + + """ + if len(stack) == 1024: + raise StackOverflowError + + return stack.append(value) diff --git a/tests/osaka/__init__.py b/tests/osaka/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/osaka/test_evm_tools.py b/tests/osaka/test_evm_tools.py new file mode 100644 index 0000000000..846a09fd1e --- /dev/null +++ b/tests/osaka/test_evm_tools.py @@ -0,0 +1,66 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH +from tests.helpers.load_evm_tools_tests import ( + fetch_evm_tools_tests, + idfn, + load_evm_tools_test, +) + +ETHEREUM_STATE_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/GeneralStateTests/" +EEST_STATE_TESTS_DIR = f"{EEST_TESTS_PATH}/state_tests/" +FORK_NAME = "Osaka" + + +SLOW_TESTS = ( + "CALLBlake2f_MaxRounds", + "CALLCODEBlake2f", + "CALLBlake2f", + "loopExp", + "loopMul", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_non-degeneracy-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_bilinearity-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(G1,-G2)=e(-G1,G2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(abG1,G2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-bls_pairing_e(aG1,bG2)=e(G1,abG2)-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-inf_pair-]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing.py::test_valid[fork_Prague-state_test-multi_inf_pair-]", +) + + +# Define tests +fetch_tests = partial( + fetch_evm_tools_tests, + fork_name=FORK_NAME, + slow_tests=SLOW_TESTS, +) + +run_tests = partial( + load_evm_tools_test, + fork_name=FORK_NAME, +) + + +# Run tests from ethereum/tests +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_STATE_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests_evm_tools(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures +@pytest.mark.evm_tools +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_STATE_TESTS_DIR), + ids=idfn, +) +def test_eest_evm_tools(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/osaka/test_rlp.py b/tests/osaka/test_rlp.py new file mode 100644 index 0000000000..2b3961dc6a --- /dev/null +++ b/tests/osaka/test_rlp.py @@ -0,0 +1,174 @@ +import pytest +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import keccak256 +from ethereum.osaka.blocks import Block, Header, Log, Receipt, Withdrawal +from ethereum.osaka.transactions import ( + Access, + AccessListTransaction, + FeeMarketTransaction, + LegacyTransaction, + Transaction, + decode_transaction, + encode_transaction, +) +from ethereum.osaka.utils.hexadecimal import hex_to_address +from ethereum.utils.hexadecimal import hex_to_bytes256 + +hash1 = keccak256(b"foo") +hash2 = keccak256(b"bar") +hash3 = keccak256(b"baz") +hash4 = keccak256(b"foobar") +hash5 = keccak256(b"quux") +hash6 = keccak256(b"foobarbaz") +hash7 = keccak256(b"quuxbaz") + +address1 = hex_to_address("0x00000000219ab540356cbb839cbe05303d7705fa") +address2 = hex_to_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") +address3 = hex_to_address("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8") + +bloom = hex_to_bytes256( + "0x886480c00200620d84180d0470000c503081160044d05015808" + "0037401107060120040105281100100104500414203040a208003" + "4814200610da1208a638d16e440c024880800301e1004c2b02285" + "0602000084c3249a0c084569c90c2002001586241041e8004035a" + "4400a0100938001e041180083180b0340661372060401428c0200" + "87410402b9484028100049481900c08034864314688d001548c30" + "00828e542284180280006402a28a0264da00ac223004006209609" + "83206603200084040122a4739080501251542082020a4087c0002" + "81c08800898d0900024047380000127038098e090801080000429" + "0c84201661040200201c0004b8490ad588804" +) + +legacy_transaction = LegacyTransaction( + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"foo"), + U256(27), + U256(5), + U256(6), +) + +access_list_transaction = AccessListTransaction( + U64(1), + U256(1), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), + U256(27), + U256(5), + U256(6), +) + +transaction_1559 = FeeMarketTransaction( + U64(1), + U256(1), + Uint(7), + Uint(2), + Uint(3), + Bytes0(), + U256(4), + Bytes(b"bar"), + ( + Access(account=address1, slots=(hash1, hash2)), + Access(account=address2, slots=()), + ), + U256(27), + U256(5), + U256(6), +) + +withdrawal = Withdrawal(U64(0), U64(1), address1, U256(2)) + + +header = Header( + parent_hash=hash1, + ommers_hash=hash2, + coinbase=address1, + state_root=hash3, + transactions_root=hash4, + receipt_root=hash5, + bloom=bloom, + difficulty=Uint(1), + number=Uint(2), + gas_limit=Uint(3), + gas_used=Uint(4), + timestamp=U256(5), + extra_data=Bytes(b"foobar"), + prev_randao=Bytes32(b"1234567890abcdef1234567890abcdef"), + nonce=Bytes8(b"12345678"), + base_fee_per_gas=Uint(6), + withdrawals_root=hash6, + parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), + blob_gas_used=U64(7), + excess_blob_gas=U64(8), + requests_hash=hash7, +) + +block = Block( + header=header, + transactions=( + encode_transaction(legacy_transaction), + encode_transaction(access_list_transaction), + encode_transaction(transaction_1559), + ), + ommers=(), + withdrawals=(withdrawal,), +) + +log1 = Log( + address=address1, + topics=(hash1, hash2), + data=Bytes(b"foobar"), +) + +log2 = Log( + address=address1, + topics=(hash1,), + data=Bytes(b"quux"), +) + +receipt = Receipt( + succeeded=True, + cumulative_gas_used=Uint(1), + bloom=bloom, + logs=(log1, log2), +) + + +@pytest.mark.parametrize( + "rlp_object", + [ + legacy_transaction, + access_list_transaction, + transaction_1559, + header, + block, + log1, + log2, + receipt, + withdrawal, + ], +) +def test_cancun_rlp(rlp_object: rlp.Extended) -> None: + encoded = rlp.encode(rlp_object) + assert rlp.decode_to(type(rlp_object), encoded) == rlp_object + + +@pytest.mark.parametrize( + "tx", [legacy_transaction, access_list_transaction, transaction_1559] +) +def test_transaction_encoding(tx: Transaction) -> None: + encoded = encode_transaction(tx) + assert decode_transaction(encoded) == tx diff --git a/tests/osaka/test_state_transition.py b/tests/osaka/test_state_transition.py new file mode 100644 index 0000000000..a543ecea3a --- /dev/null +++ b/tests/osaka/test_state_transition.py @@ -0,0 +1,104 @@ +from functools import partial +from typing import Dict + +import pytest + +from tests.helpers import EEST_TESTS_PATH, ETHEREUM_TESTS_PATH +from tests.helpers.load_state_tests import ( + Load, + fetch_state_test_files, + idfn, + run_blockchain_st_test, +) + +ETHEREUM_BLOCKCHAIN_TESTS_DIR = f"{ETHEREUM_TESTS_PATH}/BlockchainTests/" +EEST_BLOCKCHAIN_TESTS_DIR = f"{EEST_TESTS_PATH}/blockchain_tests/" +NETWORK = "Osaka" +PACKAGE = "osaka" + +SLOW_TESTS = ( + # GeneralStateTests + "stTimeConsuming/CALLBlake2f_MaxRounds.json", + "stTimeConsuming/static_Call50000_sha256.json", + "vmPerformance/loopExp.json", + "vmPerformance/loopMul.json", + "QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Prague", + "CALLBlake2f_d9g0v0_Prague", + "CALLCODEBlake2f_d9g0v0", + # GeneralStateTests + "stRandom/randomStatetest177.json", + "stCreateTest/CreateOOGafterMaxCodesize.json", + # ValidBlockTest + "bcExploitTest/DelegateCallSpam.json", + # InvalidBlockTest + "bcUncleHeaderValidity/nonceWrong.json", + "bcUncleHeaderValidity/wrongMixHash.json", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_non-degeneracy-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_bilinearity-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(G1,-G2\\)=e\\(-G1,G2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(abG1,G2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-bls_pairing_e\\(aG1,bG2\\)=e\\(G1,abG2\\)-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-inf_pair-\\]", + "tests/osaka/eip2537_bls_12_381_precompiles/test_bls12_pairing\\.py\\:\\:test_valid\\[fork_Prague-blockchain_test-multi_inf_pair-\\]", + "tests/osaka/eip2935_historical_block_hashes_from_state/test_block_hashes\\.py\\:\\:test_block_hashes_history\\[fork_Prague-blockchain_test-full_history_plus_one_check_blockhash_first\\]", +) + +# These are tests that are considered to be incorrect, +# Please provide an explanation when adding entries +IGNORE_TESTS = ( + # ValidBlockTest + "bcForkStressTest/ForkStressTest.json", + "bcGasPricerTest/RPC_API_Test.json", + "bcMultiChainTest", + "bcTotalDifficultyTest", + # InvalidBlockTest + "bcForgedTest", + "bcMultiChainTest", + "GasLimitHigherThan2p63m1_Prague", +) + +# All tests that recursively create a large number of frames (50000) +BIG_MEMORY_TESTS = ( + # GeneralStateTests + "50000_", + "/stQuadraticComplexityTest/", + "/stRandom2/", + "/stRandom/", + "/stSpecialTest/", + "stTimeConsuming/", + "stBadOpcode/", + "stStaticCall/", +) + +# Define Tests +fetch_tests = partial( + fetch_state_test_files, + network=NETWORK, + ignore_list=IGNORE_TESTS, + slow_list=SLOW_TESTS, + big_memory_list=BIG_MEMORY_TESTS, +) + +FIXTURES_LOADER = Load(NETWORK, PACKAGE) + +run_tests = partial(run_blockchain_st_test, load=FIXTURES_LOADER) + + +# Run tests from ethereum/tests +@pytest.mark.parametrize( + "test_case", + fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_ethereum_tests(test_case: Dict) -> None: + run_tests(test_case) + + +# Run EEST test fixtures +@pytest.mark.parametrize( + "test_case", + fetch_tests(EEST_BLOCKCHAIN_TESTS_DIR), + ids=idfn, +) +def test_eest_tests(test_case: Dict) -> None: + run_tests(test_case) diff --git a/tests/osaka/test_trie.py b/tests/osaka/test_trie.py new file mode 100644 index 0000000000..04c86a1e08 --- /dev/null +++ b/tests/osaka/test_trie.py @@ -0,0 +1,89 @@ +import json +from typing import Any + +from ethereum.osaka.fork_types import Bytes +from ethereum.osaka.trie import Trie, root, trie_set +from ethereum.utils.hexadecimal import ( + has_hex_prefix, + hex_to_bytes, + remove_hex_prefix, +) +from tests.helpers import TEST_FIXTURES + +FIXTURE_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] + + +def to_bytes(data: str) -> Bytes: + if data is None: + return b"" + if has_hex_prefix(data): + return hex_to_bytes(data) + + return data.encode() + + +def test_trie_secure_hex() -> None: + tests = load_tests("hex_encoded_securetrie_test.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure() -> None: + tests = load_tests("trietest_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_secure_any_order() -> None: + tests = load_tests("trieanyorder_secureTrie.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=True, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie() -> None: + tests = load_tests("trietest.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for t in test.get("in"): + trie_set(st, to_bytes(t[0]), to_bytes(t[1])) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def test_trie_any_order() -> None: + tests = load_tests("trieanyorder.json") + + for name, test in tests.items(): + st: Trie[Bytes, Bytes] = Trie(secured=False, default=b"") + for k, v in test.get("in").items(): + trie_set(st, to_bytes(k), to_bytes(v)) + result = root(st) + expected = remove_hex_prefix(test.get("root")) + assert result.hex() == expected, f"test {name} failed" + + +def load_tests(path: str) -> Any: + with open(f"{FIXTURE_PATH}/TrieTests/" + path) as f: + tests = json.load(f) + + return tests From 1d623fb57bdf3d67bbc3a0e05cb1b1b8d8d7ff3d Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath Date: Thu, 8 May 2025 16:17:59 +0200 Subject: [PATCH 65/70] update specific osaka refs --- setup.cfg | 6 ++++++ src/ethereum/osaka/__init__.py | 4 ++-- src/ethereum/osaka/trie.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 10bdb5a610..e545910289 100644 --- a/setup.cfg +++ b/setup.cfg @@ -116,6 +116,12 @@ packages = ethereum/prague/vm/instructions ethereum/prague/vm/precompiled_contracts ethereum/prague/vm/precompiled_contracts/bls12_381 + ethereum/osaka + ethereum/osaka/utils + ethereum/osaka/vm + ethereum/osaka/vm/instructions + ethereum/osaka/vm/precompiled_contracts + ethereum/osaka/vm/precompiled_contracts/bls12_381 package_dir = diff --git a/src/ethereum/osaka/__init__.py b/src/ethereum/osaka/__init__.py index 803000ba44..8c5c37298a 100644 --- a/src/ethereum/osaka/__init__.py +++ b/src/ethereum/osaka/__init__.py @@ -2,6 +2,6 @@ The Osaka fork. """ -from ethereum.fork_criteria import ByTimestamp +from ethereum.fork_criteria import Unscheduled -FORK_CRITERIA = ByTimestamp(1746612311) +FORK_CRITERIA = Unscheduled() diff --git a/src/ethereum/osaka/trie.py b/src/ethereum/osaka/trie.py index 0c6cbdba30..339c48a62d 100644 --- a/src/ethereum/osaka/trie.py +++ b/src/ethereum/osaka/trie.py @@ -36,8 +36,8 @@ from ethereum_types.numeric import U256, Uint from typing_extensions import assert_type -from ethereum.cancun import trie as previous_trie from ethereum.crypto.hash import keccak256 +from ethereum.prague import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes from .blocks import Receipt, Withdrawal From 0550bf3efe9db1d0298b8b1b77f617dff4844ac3 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 29 May 2025 00:10:51 -0600 Subject: [PATCH 66/70] implement eip7805 --- src/ethereum/osaka/blocks.py | 1 + src/ethereum/osaka/fork.py | 38 +++++++++++++++++++ .../evm_tools/loaders/fork_loader.py | 5 +++ .../evm_tools/t8n/__init__.py | 8 ++++ src/ethereum_spec_tools/evm_tools/t8n/env.py | 17 +++++++++ 5 files changed, 69 insertions(+) diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py index 80b29087e9..d445fd59c5 100644 --- a/src/ethereum/osaka/blocks.py +++ b/src/ethereum/osaka/blocks.py @@ -82,6 +82,7 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index 7a75825a4c..8da3e31aeb 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -216,6 +216,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, + inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -578,6 +579,7 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], + inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -597,6 +599,8 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. + inclusion_list : + Transactions that must be included in the block if possible. Returns ------- @@ -620,6 +624,13 @@ def apply_body( for i, tx in enumerate(map(decode_transaction, transactions)): process_transaction(block_env, block_output, tx, Uint(i)) + validate_inclusion_list( + block_env, + block_output, + transactions, + inclusion_list, + ) + process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -867,6 +878,33 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) +def validate_inclusion_list( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + transactions: Tuple[Union[Bytes, LegacyTransaction], ...], + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], +) -> None: + """ + Validate the block satisfies the inclusion list. + """ + + index = Uint(len(transactions)) + for tx in inclusion_list: + # If the transaction is already present in the block, then skip. + if tx in transactions: + continue + + try: + tx = decode_transaction(tx) + process_transaction(block_env, block_output, tx, index) + except Exception as e: + continue + + # If the transaction was not in the block and was decoded and + # executed successfully, then mark the block invalid. + raise InvalidBlock("unsatisfied inclusion list") + + def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 0877cb812b..66ce9e62cb 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -147,6 +147,11 @@ def process_transaction(self) -> Any: """process_transaction function of the fork""" return self._module("fork").process_transaction + @property + def validate_inclusion_list(self) -> Any: + """validate_inclusion_list function of the fork""" + return self._module("fork").validate_inclusion_list + @property def MAX_BLOB_GAS_PER_BLOCK(self) -> Any: """MAX_BLOB_GAS_PER_BLOCK parameter of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 50e02cb584..7c4bf4e150 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -235,6 +235,14 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: self.env.ommers, ) + if self.fork.is_after_fork("ethereum.osaka"): + self.fork.validate_inclusion_list( + block_env, + block_output, + self.txs.transactions, + self.env.inclusion_list, + ) + if self.fork.is_after_fork("ethereum.shanghai"): self.fork.process_withdrawals( block_env, block_output, self.env.withdrawals diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index d1cff3975a..2a7582697b 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -54,11 +54,16 @@ class Env: parent_blob_gas_used: Optional[U64] excess_blob_gas: Optional[U64] requests: Any + inclusion_list: Any def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): + inclusion_list = None + if t8n.options.input_env == "stdin": assert stdin is not None + print(list(stdin.keys())) data = stdin["env"] + inclusion_list = stdin["inclusionList"] else: with open(t8n.options.input_env, "r") as f: data = json.load(f) @@ -75,6 +80,8 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): self.read_ommers(data, t8n) self.read_withdrawals(data, t8n) + self.read_inclusion_list(inclusion_list, t8n) + self.parent_beacon_block_root = None if t8n.fork.is_after_fork("ethereum.cancun"): if not t8n.options.state_test: @@ -191,6 +198,16 @@ def read_withdrawals(self, data: Any, t8n: "T8N") -> None: t8n.json_to_withdrawals(wd) for wd in data["withdrawals"] ) + def read_inclusion_list(self, inclusion_list: Any, t8n: "T8N") -> None: + """ + Read the inclusion list from the data. + """ + self.inclusion_list = None + if t8n.fork.is_after_fork("ethereum.osaka"): + self.inclusion_list = tuple( + hex_to_bytes(tx) for tx in inclusion_list + ) + def read_block_difficulty(self, data: Any, t8n: "T8N") -> None: """ Read the block difficulty from the data. From ecfb525ddfbc031bc32407c46454acea1d4b9e3e Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 29 May 2025 00:41:42 -0600 Subject: [PATCH 67/70] fix: remove leftover merge artifact --- src/ethereum/prague/fork.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index 4fd78c5f2f..70f5578dd8 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -659,7 +659,6 @@ def process_unchecked_system_transaction( ) ->>>>>>> upstream/eips/osaka/eip-7805 def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], From 5e268b0fb6dd1bd205d227a97dc4947268216f5f Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 29 May 2025 13:43:02 -0600 Subject: [PATCH 68/70] remove debug print statement from t8n --- src/ethereum_spec_tools/evm_tools/t8n/env.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index 2a7582697b..e131cdd0c7 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -61,7 +61,6 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): if t8n.options.input_env == "stdin": assert stdin is not None - print(list(stdin.keys())) data = stdin["env"] inclusion_list = stdin["inclusionList"] else: From 441801ed82e922352bf4ce1374406a345c89b671 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 29 May 2025 13:45:06 -0600 Subject: [PATCH 69/70] add fix from pr 1233 --- src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 97ac1f6989..d99b7782b9 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -339,18 +339,10 @@ def json_encode_receipts(self) -> Any: receipt_dict["gasUsed"] = hex(receipt.cumulative_gas_used) receipt_dict["bloom"] = "0x" + receipt.bloom.hex() - receipt_dict["logs"] = [ - { - "address": "0x" + log.address.hex(), - "topics": ["0x" + topic.hex() for topic in log.topics], - "data": "0x" + log.data.hex(), - } - for log in receipt.logs - ] receipts_json.append(receipt_dict) - return receipt_dict + return receipts_json def to_json(self) -> Any: """Encode the result to JSON""" From 5be696593da7e373fe7c9b3d10b790b998f84d07 Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 29 May 2025 22:42:27 -0600 Subject: [PATCH 70/70] fix: validate IL in blockchain test --- src/ethereum_spec_tools/evm_tools/t8n/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 5f979297ca..cb92833000 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -235,6 +235,14 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: self.env.ommers, ) + if self.fork.is_after_fork("ethereum.osaka"): + self.fork.validate_inclusion_list( + block_env, + block_output, + self.txs.transactions, + self.env.inclusion_list, + ) + if self.fork.is_after_fork("ethereum.shanghai"): self.fork.process_withdrawals( block_env, block_output, self.env.withdrawals