diff --git a/src/ethereum/base_types.py b/src/ethereum/base_types.py index b8c242aa40..17a58c5c83 100644 --- a/src/ethereum/base_types.py +++ b/src/ethereum/base_types.py @@ -710,6 +710,41 @@ def to_signed(self) -> int: U256.MAX_VALUE = int.__new__(U256, (2**256) - 1) +class U8(FixedUint): + """ + Unsigned positive integer, which can represent `0` to `2 ** 8 - 1`, + inclusive. + """ + + MAX_VALUE: ClassVar["U8"] + """ + Largest value that can be represented by this integer type. + """ + + __slots__ = () + + @classmethod + def from_le_bytes(cls: Type, buffer: "Bytes1") -> "U8": + """ + Converts a sequence of bytes into an arbitrarily sized unsigned integer + from its little endian representation. + """ + if len(buffer) > 1: + raise ValueError() + + return cls(int.from_bytes(buffer, "little")) + + def to_le_bytes(self) -> "Bytes1": + """ + Converts this fixed sized unsigned integer into its little endian + representation, with exactly 1 byte. + """ + return Bytes1(self.to_bytes(1, "little")) + + +U8.MAX_VALUE = int.__new__(U8, (2**8) - 1) + + class U32(FixedUint): """ Unsigned positive integer, which can represent `0` to `2 ** 32 - 1`, @@ -848,6 +883,17 @@ class Bytes0(FixedBytes): """ +class Bytes1(FixedBytes): + """ + Byte array of exactly a single byte. + """ + + LENGTH = 1 + """ + Number of bytes in each instance of this class. + """ + + class Bytes4(FixedBytes): """ Byte array of exactly four elements. diff --git a/src/ethereum/genesis.py b/src/ethereum/genesis.py index cf8e2592cf..281a4a91bb 100644 --- a/src/ethereum/genesis.py +++ b/src/ethereum/genesis.py @@ -263,8 +263,8 @@ 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_root"): - fields["requests_root"] = hardfork.root(hardfork.Trie(False, None)) + if has_field(hardfork.Header, "requests_hash"): + fields["requests_hash"] = Hash32(b"\0" * 32) genesis_header = hardfork.Header(**fields) diff --git a/src/ethereum/prague/blocks.py b/src/ethereum/prague/blocks.py index d82cc2cf18..45f14facec 100644 --- a/src/ethereum/prague/blocks.py +++ b/src/ethereum/prague/blocks.py @@ -73,7 +73,7 @@ class Header: blob_gas_used: U64 excess_blob_gas: U64 parent_beacon_block_root: Root - requests_root: Root + requests_hash: Hash32 @slotted_freezable @@ -87,7 +87,6 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] - requests: Tuple[Bytes, ...] @slotted_freezable diff --git a/src/ethereum/prague/fork.py b/src/ethereum/prague/fork.py index f0c772fd21..d39183a9c0 100644 --- a/src/ethereum/prague/fork.py +++ b/src/ethereum/prague/fork.py @@ -28,9 +28,11 @@ from .bloom import logs_bloom from .fork_types import Address, Authorization, Bloom, Root, VersionedHash from .requests import ( - parse_consolidation_requests_from_system_tx, + CONSOLIDATION_REQUEST_TYPE, + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + compute_requests_hash, parse_deposit_requests_from_receipt, - parse_withdrawal_requests_from_system_tx, ) from .state import ( State, @@ -91,10 +93,10 @@ "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e" ) WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x00A3ca265EBcb825B45F985A16CEFB49958cE017" + "0x09Fc772D0857550724b07B850a4323f39112aAaA" ) CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( - "0x00b42dbf2194e931e80326d950320f7d9dbeac02" + "0x01aBEa29659e5e97C95107F20bb753cD3e09bBBb" ) SYSTEM_TRANSACTION_GAS = Uint(30000000) MAX_BLOB_GAS_PER_BLOCK = 786432 @@ -220,7 +222,6 @@ def state_transition(chain: BlockChain, block: Block) -> None: block.withdrawals, block.header.parent_beacon_block_root, excess_blob_gas, - block.requests, ) if apply_body_output.block_gas_used != block.header.gas_used: raise InvalidBlock @@ -236,7 +237,7 @@ 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_root != block.header.requests_root: + if apply_body_output.requests_hash != block.header.requests_hash: raise InvalidBlock chain.blocks.append(block) @@ -522,8 +523,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_root : `ethereum.fork_types.Root` - Trie root of all the requests in the block. + requests_hash : `Bytes` + Hash of all the requests in the block. """ block_gas_used: Uint @@ -533,7 +534,7 @@ class ApplyBodyOutput: state_root: Root withdrawals_root: Root blob_gas_used: Uint - requests_root: Root + requests_hash: Bytes def process_system_transaction( @@ -652,7 +653,6 @@ def apply_body( withdrawals: Tuple[Withdrawal, ...], parent_beacon_block_root: Root, excess_blob_gas: U64, - requests: Tuple[Bytes, ...], ) -> ApplyBodyOutput: """ Executes a block. @@ -696,8 +696,6 @@ def apply_body( The root of the beacon block from the parent block. excess_blob_gas : Excess blob gas calculated from the previous block. - requests : - Requests to be processed in the current block. Returns ------- @@ -715,11 +713,8 @@ def apply_body( withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie( secured=False, default=None ) - requests_trie: Trie[Bytes, Optional[Bytes]] = Trie( - secured=False, default=None - ) block_logs: Tuple[Log, ...] = () - requests_from_execution: Tuple[Bytes, ...] = () + deposit_requests: Bytes = b"" process_system_transaction( BEACON_ROOTS_ADDRESS, @@ -801,8 +796,7 @@ def apply_body( receipt, ) - deposit_requests = parse_deposit_requests_from_receipt(receipt) - requests_from_execution += deposit_requests + deposit_requests += parse_deposit_requests_from_receipt(receipt) block_logs += logs blob_gas_used += calculate_total_blob_gas(tx) @@ -820,7 +814,83 @@ 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), + root(receipts_trie), + block_logs_bloom, + 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] = [] + requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests) system_withdrawal_tx_output = process_system_transaction( WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, @@ -837,12 +907,10 @@ def apply_body( excess_blob_gas, ) - withdrawal_requests = parse_withdrawal_requests_from_system_tx( - system_withdrawal_tx_output.return_data + requests_from_execution.append( + WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data ) - requests_from_execution += withdrawal_requests - system_consolidation_tx_output = process_system_transaction( CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, b"", @@ -858,28 +926,11 @@ def apply_body( excess_blob_gas, ) - consolidation_requests = parse_consolidation_requests_from_system_tx( - system_consolidation_tx_output.return_data + requests_from_execution.append( + CONSOLIDATION_REQUEST_TYPE + system_consolidation_tx_output.return_data ) - requests_from_execution += consolidation_requests - - if requests_from_execution != requests: - raise InvalidBlock - - for i, request in enumerate(requests_from_execution): - trie_set(requests_trie, rlp.encode(Uint(i)), request) - - return ApplyBodyOutput( - block_gas_used, - root(transactions_trie), - root(receipts_trie), - block_logs_bloom, - state_root(state), - root(withdrawals_trie), - blob_gas_used, - root(requests_trie), - ) + return requests_from_execution def process_transaction( diff --git a/src/ethereum/prague/fork_types.py b/src/ethereum/prague/fork_types.py index 1130937187..4e33e4f6e6 100644 --- a/src/ethereum/prague/fork_types.py +++ b/src/ethereum/prague/fork_types.py @@ -16,6 +16,7 @@ from .. import rlp from ..base_types import ( + U8, U64, U256, Bytes, @@ -79,6 +80,6 @@ class Authorization: chain_id: U64 address: Address nonce: U64 - y_parity: U256 + y_parity: U8 r: U256 s: U256 diff --git a/src/ethereum/prague/requests.py b/src/ethereum/prague/requests.py index 509c92f839..2abd77d5c7 100644 --- a/src/ethereum/prague/requests.py +++ b/src/ethereum/prague/requests.py @@ -8,20 +8,11 @@ [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 """ -from dataclasses import dataclass -from typing import Tuple, Union +from hashlib import sha256 +from typing import List, Union -from .. import rlp -from ..base_types import ( - U64, - Bytes, - Bytes32, - Bytes48, - Bytes96, - slotted_freezable, -) +from ..base_types import Bytes from .blocks import Receipt, decode_receipt -from .fork_types import Address from .utils.hexadecimal import hex_to_address DEPOSIT_CONTRACT_ADDRESS = hex_to_address( @@ -30,178 +21,53 @@ DEPOSIT_REQUEST_TYPE = b"\x00" WITHDRAWAL_REQUEST_TYPE = b"\x01" CONSOLIDATION_REQUEST_TYPE = b"\x02" -WITHDRAWAL_REQUEST_LENGTH = 76 -CONSOLIDATION_REQUEST_LENGTH = 116 - - -@slotted_freezable -@dataclass -class DepositRequest: - """ - Requests for validator deposits on chain (See [EIP-6110]). - - [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110 - """ - - public_key: Bytes48 - withdrawal_credentials: Bytes32 - amount: U64 - signature: Bytes96 - index: U64 - - -@slotted_freezable -@dataclass -class WithdrawalRequest: - """ - Requests for execution layer withdrawals (See [EIP-7002]). - - [EIP-7002]: https://eips.ethereum.org/EIPS/eip-7002 - """ - source_address: Address - validator_public_key: Bytes48 - amount: U64 - -@slotted_freezable -@dataclass -class ConsolidationRequest: - """ - Requests for validator consolidation (See [EIP-7251]). - - [EIP-7251]: https://eips.ethereum.org/EIPS/eip-7251 - """ - - source_address: Address - source_public_key: Bytes48 - target_public_key: Bytes48 - - -Request = Union[DepositRequest, WithdrawalRequest, ConsolidationRequest] - - -def encode_request(req: Request) -> Bytes: +def extract_deposit_data(data: Bytes) -> Bytes: """ - Serialize a `Request` into a byte sequence. - - `Request`s are encoded as a type byte followed by the RLP encoding - of the request. + Extracts Deposit Request from the DepositContract.DepositEvent data. """ - if isinstance(req, DepositRequest): - return DEPOSIT_REQUEST_TYPE + rlp.encode(req) - elif isinstance(req, WithdrawalRequest): - return WITHDRAWAL_REQUEST_TYPE + rlp.encode(req) - elif isinstance(req, ConsolidationRequest): - return CONSOLIDATION_REQUEST_TYPE + rlp.encode(req) - else: - raise Exception("Unknown request type") - - -def decode_request(data: Bytes) -> Request: - """ - Decode a request. - """ - if data.startswith(DEPOSIT_REQUEST_TYPE): - return rlp.decode_to(DepositRequest, data[1:]) - elif data.startswith(WITHDRAWAL_REQUEST_TYPE): - return rlp.decode_to(WithdrawalRequest, data[1:]) - elif data.startswith(CONSOLIDATION_REQUEST_TYPE): - return rlp.decode_to(ConsolidationRequest, data[1:]) - else: - raise Exception("Unknown request type") - - -def parse_deposit_data(data: Bytes) -> DepositRequest: - """ - Parses Deposit Request from the DepositContract.DepositEvent data. - """ - return DepositRequest( - public_key=Bytes48(data[192:240]), - withdrawal_credentials=Bytes32(data[288:320]), - amount=U64.from_le_bytes(data[352:360]), - signature=Bytes96(data[416:512]), - index=U64.from_le_bytes(data[544:552]), + 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], -) -> Tuple[Bytes, ...]: +) -> Bytes: """ Parse deposit requests from a receipt. """ - deposit_requests: Tuple[Bytes, ...] = () + deposit_requests: Bytes = b"" decoded_receipt = decode_receipt(receipt) for log in decoded_receipt.logs: if log.address == DEPOSIT_CONTRACT_ADDRESS: - deposit_request = parse_deposit_data(log.data) - deposit_requests += (encode_request(deposit_request),) + request = extract_deposit_data(log.data) + deposit_requests += request return deposit_requests -def parse_withdrawal_data(data: Bytes) -> WithdrawalRequest: +def compute_requests_hash(requests: List[Bytes]) -> Bytes: """ - Parses Withdrawal Request from the data. - """ - assert len(data) == WITHDRAWAL_REQUEST_LENGTH - return WithdrawalRequest( - source_address=Address(data[:20]), - validator_public_key=Bytes48(data[20:68]), - amount=U64.from_be_bytes(data[68:76]), - ) + Get the hash of the requests using the SHA2-256 algorithm. + Parameters + ---------- + requests : Bytes + The requests to hash. -def parse_withdrawal_requests_from_system_tx( - evm_call_output: Bytes, -) -> Tuple[Bytes, ...]: - """ - Parse withdrawal requests from the system transaction output. + Returns + ------- + requests_hash : Bytes + The hash of the requests. """ - count_withdrawal_requests = ( - len(evm_call_output) // WITHDRAWAL_REQUEST_LENGTH - ) - - withdrawal_requests: Tuple[Bytes, ...] = () - for i in range(count_withdrawal_requests): - start = i * WITHDRAWAL_REQUEST_LENGTH - withdrawal_request = parse_withdrawal_data( - evm_call_output[start : start + WITHDRAWAL_REQUEST_LENGTH] - ) - withdrawal_requests += (encode_request(withdrawal_request),) - - return withdrawal_requests - - -def parse_consolidation_data(data: Bytes) -> ConsolidationRequest: - """ - Parses Consolidation Request from the data. - """ - assert len(data) == CONSOLIDATION_REQUEST_LENGTH - return ConsolidationRequest( - source_address=Address(data[:20]), - source_public_key=Bytes48(data[20:68]), - target_public_key=Bytes48(data[68:116]), - ) - - -def parse_consolidation_requests_from_system_tx( - evm_call_output: Bytes, -) -> Tuple[Bytes, ...]: - """ - Parse consolidation requests from the system transaction output. - """ - count_consolidation_requests = ( - len(evm_call_output) // CONSOLIDATION_REQUEST_LENGTH - ) - - consolidation_requests: Tuple[Bytes, ...] = () - for i in range(count_consolidation_requests): - start = i * CONSOLIDATION_REQUEST_LENGTH - consolidation_request = parse_consolidation_data( - evm_call_output[start : start + CONSOLIDATION_REQUEST_LENGTH] - ) - consolidation_requests += (encode_request(consolidation_request),) + m = sha256() + for request in requests: + m.update(sha256(request).digest()) - return consolidation_requests + return m.digest() diff --git a/src/ethereum/prague/utils/message.py b/src/ethereum/prague/utils/message.py index ce68621913..7c45543fc0 100644 --- a/src/ethereum/prague/utils/message.py +++ b/src/ethereum/prague/utils/message.py @@ -19,6 +19,7 @@ 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 @@ -78,6 +79,11 @@ def prepare_message( 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, @@ -89,16 +95,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, diff --git a/src/ethereum/prague/vm/eoa_delegation.py b/src/ethereum/prague/vm/eoa_delegation.py index 0746c334d8..ce13b891ab 100644 --- a/src/ethereum/prague/vm/eoa_delegation.py +++ b/src/ethereum/prague/vm/eoa_delegation.py @@ -3,7 +3,7 @@ """ -from typing import Tuple +from typing import Optional, Tuple from ethereum import rlp from ethereum.base_types import U64, U256, Bytes, Uint @@ -14,6 +14,7 @@ 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 @@ -22,7 +23,8 @@ EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) EOA_DELEGATED_CODE_LENGTH = 23 PER_EMPTY_ACCOUNT_COST = 25000 -PER_AUTH_BASE_COST = 2500 +PER_AUTH_BASE_COST = 12500 +NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") def is_valid_delegation(code: bytes) -> bool: @@ -48,6 +50,25 @@ def is_valid_delegation(code: bytes) -> bool: 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. @@ -86,7 +107,7 @@ def recover_authority(authorization: Authorization) -> Address: ) ) - public_key = secp256k1_recover(r, s, y_parity, signing_hash) + public_key = secp256k1_recover(r, s, U256(y_parity), signing_hash) return Address(keccak256(public_key)[12:32]) @@ -144,6 +165,9 @@ def set_delegation(message: Message, env: Environment) -> U256: if auth.chain_id not in (env.chain_id, U64(0)): continue + if auth.nonce >= U64.MAX_VALUE: + continue + try: authority = recover_authority(auth) except InvalidSignature: @@ -166,7 +190,10 @@ def set_delegation(message: Message, env: Environment) -> U256: if account_exists(env.state, authority): refund_counter += PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST - code_to_set = EOA_DELEGATION_MARKER + auth.address + 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) diff --git a/src/ethereum/prague/vm/interpreter.py b/src/ethereum/prague/vm/interpreter.py index 722ba1b492..7511854d84 100644 --- a/src/ethereum/prague/vm/interpreter.py +++ b/src/ethereum/prague/vm/interpreter.py @@ -126,7 +126,7 @@ def process_message_call( else: evm = process_create_message(message, env) else: - if message.authorizations != set(): + 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)): diff --git a/src/ethereum/utils/hexadecimal.py b/src/ethereum/utils/hexadecimal.py index acfe18748c..8f048ec5c5 100644 --- a/src/ethereum/utils/hexadecimal.py +++ b/src/ethereum/utils/hexadecimal.py @@ -12,6 +12,7 @@ Hexadecimal strings specific utility functions used in this specification. """ from ethereum.base_types import ( + U8, U64, U256, Bytes, @@ -183,6 +184,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/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index 7e85e10b4e..e9df1b67f8 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -177,8 +177,8 @@ def json_to_header(self, raw: Any) -> Any: ) parameters.append(parent_beacon_block_root) - if "requestsRoot" in raw: - requests_root = self.fork.hex_to_root(raw.get("requestsRoot")) - parameters.append(requests_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 3ca8d6adc2..8d2a51ca1b 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -82,6 +82,11 @@ 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.""" + return self._module("fork").process_general_purpose_requests + @property def process_system_transaction(self) -> Any: """process_system_transaction function of the given fork.""" @@ -172,44 +177,15 @@ def Block(self) -> Any: """Block class of the fork""" return self._module("blocks").Block - @property - def DepositRequest(self) -> Any: - """Deposit request of the fork""" - return self._module("requests").DepositRequest - - @property - def WithdrawalRequest(self) -> Any: - """Withdrawal request of the fork""" - return self._module("requests").WithdrawalRequest - - @property - def ConsolidationRequest(self) -> Any: - """Consolidation request of the fork""" - return self._module("requests").ConsolidationRequest - - @property - def decode_request(self) -> Any: - """decode_request function of the fork""" - return self._module("requests").decode_request - - @property - def parse_withdrawal_requests_from_system_tx(self) -> Any: - """parse_withdrawal_requests_from_system_tx function of the fork""" - return self._module( - "requests" - ).parse_withdrawal_requests_from_system_tx - @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 parse_consolidation_requests_from_system_tx(self) -> Any: - """parse_deposit_requests_from_receipt function of the fork""" - return self._module( - "requests" - ).parse_consolidation_requests_from_system_tx + 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: 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 6826b894f7..6f32b7e91e 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -12,6 +12,7 @@ hex_to_bytes, hex_to_bytes32, hex_to_hash, + hex_to_u8, hex_to_u64, hex_to_u256, hex_to_uint, @@ -91,10 +92,10 @@ def json_to_authorizations(self) -> Any: for sublist in self.raw["authorizationList"]: authorizations.append( self.fork.Authorization( - chain_id=hex_to_u256(sublist.get("chainId")), + chain_id=hex_to_u64(sublist.get("chainId")), nonce=hex_to_u64(sublist.get("nonce")), address=self.fork.hex_to_address(sublist.get("address")), - y_parity=hex_to_u256(sublist.get("v")), + y_parity=hex_to_u8(sublist.get("v")), r=hex_to_u256(sublist.get("r")), s=hex_to_u256(sublist.get("s")), ) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 2b14da4f7b..1340f37ea5 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -6,7 +6,7 @@ import json import os from functools import partial -from typing import Any, TextIO, Tuple +from typing import Any, TextIO from ethereum import rlp, trace from ethereum.base_types import U64, U256, Bytes, Uint @@ -289,18 +289,6 @@ def restore_state(self) -> None: state = self.alloc.state state._main_trie, state._storage_tries = self.alloc.state_backup - def decode_request(self, decoded_requests: Any, request: Any) -> Any: - """Decode a request.""" - req = self.fork.decode_request(request) - if isinstance(req, self.fork.DepositRequest): - decoded_requests.append(("depositRequests", req)) - elif isinstance(req, self.fork.WithdrawalRequest): - decoded_requests.append(("withdrawalRequests", req)) - elif isinstance(req, self.fork.ConsolidationRequest): - decoded_requests.append(("consolidationRequests", req)) - else: - raise Exception("Unknown request type") - def apply_body(self) -> None: """ The apply body function is seen as the entry point of @@ -320,8 +308,7 @@ def apply_body(self) -> None: self.fork.is_after_fork("ethereum.prague") and not self.options.state_test ): - requests_trie = self.fork.Trie(secured=False, default=None) - requests_from_execution: Tuple[Bytes, ...] = () + deposit_requests: Bytes = b"" self.fork.process_system_transaction( self.fork.HISTORY_STORAGE_ADDRESS, @@ -405,10 +392,9 @@ def apply_body(self) -> None: self.fork.is_after_fork("ethereum.prague") and not self.options.state_test ): - deposit_requests = ( + deposit_requests += ( self.fork.parse_deposit_requests_from_receipt(receipt) ) - requests_from_execution += deposit_requests self.txs.add_receipt(tx, gas_consumed) @@ -453,33 +439,10 @@ def apply_body(self) -> None: self.fork.is_after_fork("ethereum.prague") and not self.options.state_test ): - system_withdrawal_tx_output = self.fork.process_system_transaction( - self.fork.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - b"", - 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, - ) - - withdrawal_requests = ( - self.fork.parse_withdrawal_requests_from_system_tx( - system_withdrawal_tx_output.return_data - ) - ) - - requests_from_execution += withdrawal_requests - - system_consolidation_tx_output = ( - self.fork.process_system_transaction( - self.fork.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, - b"", + 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, @@ -487,25 +450,14 @@ def apply_body(self) -> None: self.env.block_gas_limit, self.env.block_timestamp, self.env.prev_randao, - self.alloc.state, self.chain_id, self.env.excess_blob_gas, ) ) - - consolidation_requests = ( - self.fork.parse_consolidation_requests_from_system_tx( - system_consolidation_tx_output.return_data - ) + requests_hash = self.fork.compute_requests_hash( + requests_from_execution ) - requests_from_execution += consolidation_requests - - decoded_requests: Any = [] - for i, request in enumerate(requests_from_execution): - self.fork.trie_set(requests_trie, rlp.encode(Uint(i)), request) - self.decode_request(decoded_requests, request) - 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) @@ -519,8 +471,8 @@ def apply_body(self) -> None: self.fork.is_after_fork("ethereum.prague") and not self.options.state_test ): - self.result.requests_root = self.fork.root(requests_trie) - self.result.requests = decoded_requests + self.result.requests_hash = requests_hash + self.result.requests = requests_from_execution 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 b079e49000..e811b57218 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -7,7 +7,7 @@ from ethereum import rlp from ethereum.base_types import U64, Bytes, 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 @@ -55,15 +55,15 @@ def to_json(self) -> Any: for address, account in self.state._main_trie._data.items(): account_data: Dict[str, Any] = {} + if account.code: + account_data["code"] = "0x" + account.code.hex() + if account.balance: account_data["balance"] = hex(account.balance) if account.nonce: account_data["nonce"] = hex(account.nonce) - if account.code: - account_data["code"] = "0x" + account.code.hex() - if address in self.state._storage_tries: account_data["storage"] = { "0x" + k.hex(): hex(v) @@ -306,32 +306,8 @@ class Result: gas_used: Any = None excess_blob_gas: Optional[U64] = None blob_gas_used: Optional[Uint] = None - requests_root: Optional[Bytes] = None - requests: Optional[Any] = None - - def generic_request_to_json(self, request: Any) -> Any: - """Convert a request to JSON""" - data = {} - for attr in request.__annotations__: - data[attr] = encode_to_hex(getattr(request, attr)) - - if "public_key" in data: - data["pubkey"] = data["public_key"] - del data["public_key"] - - if "validator_public_key" in data: - data["validator_pubkey"] = data["validator_public_key"] - del data["validator_public_key"] - - if "target_public_key" in data: - data["target_pubkey"] = data["target_public_key"] - del data["target_public_key"] - - if "source_public_key" in data: - data["source_pubkey"] = data["source_public_key"] - del data["source_public_key"] - - return data + requests_hash: Optional[Hash32] = None + requests: Optional[Bytes] = None def to_json(self) -> Any: """Encode the result to JSON""" @@ -361,9 +337,6 @@ def to_json(self) -> Any: if self.blob_gas_used is not None: data["blobGasUsed"] = hex(self.blob_gas_used) - if self.requests_root: - data["requestsRoot"] = "0x" + self.requests_root.hex() - data["rejected"] = [ {"index": idx, "error": error} for idx, error in self.rejected.items() @@ -377,14 +350,10 @@ def to_json(self) -> Any: for item in self.receipts ] - if self.requests_root: - data["requestsRoot"] = "0x" + self.requests_root.hex() - data["depositRequests"] = [] - data["withdrawalRequests"] = [] - data["consolidationRequests"] = [] + if self.requests_hash is not None: assert self.requests is not None - for type, req in self.requests: - json_req = self.generic_request_to_json(req) - data[type].append(json_req) + + data["requestsHash"] = encode_to_hex(self.requests_hash) + data["requests"] = [encode_to_hex(req) for req in self.requests] return data diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 51d1c52043..acbb25fc46 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": "0a8ce60", + "commit_hash": "aec85b9", "fixture_path": "tests/fixtures/latest_fork_tests", }, } diff --git a/tests/prague/test_evm_tools.py b/tests/prague/test_evm_tools.py index 7881138def..f8fbf2b56c 100644 --- a/tests/prague/test_evm_tools.py +++ b/tests/prague/test_evm_tools.py @@ -39,14 +39,7 @@ "tests/fixtures/latest_fork_tests/state_tests/prague/eip7702_set_code_tx", ) -IGNORE_TESTS = ( - # TODO: In there current verion of the test fixtures, the following tests are incorrectly filled. - # Hence, they will be ignored for now. This condition will be removed once the test fixtures are updated. - "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_2,r_1,s_1]", - "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_0,r_1,s_SECP256K1N_OVER_2+1]", - "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_2**256-1,r_1,s_1]", - "tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_invalid_tx_invalid_auth_signature[fork_Prague-state_test-v_0,r_1,s_2**256-1]", -) +IGNORE_TESTS = () def fetch_temporary_tests(test_dirs: Tuple[str, ...]) -> Generator: diff --git a/tests/prague/test_rlp.py b/tests/prague/test_rlp.py index 4b499b56e2..d9035f1a3a 100644 --- a/tests/prague/test_rlp.py +++ b/tests/prague/test_rlp.py @@ -83,9 +83,6 @@ withdrawal = Withdrawal(U64(0), U64(1), address1, U256(2)) -requests = (Bytes(b"01foo"), Bytes(b"01bar")) - - header = Header( parent_hash=hash1, ommers_hash=hash2, @@ -107,7 +104,7 @@ parent_beacon_block_root=Bytes32(b"1234567890abcdef1234567890abcdef"), blob_gas_used=U64(7), excess_blob_gas=U64(8), - requests_root=hash7, + requests_hash=hash7, ) block = Block( @@ -119,7 +116,6 @@ ), ommers=(), withdrawals=(withdrawal,), - requests=requests, ) log1 = Log( diff --git a/tests/prague/test_state_transition.py b/tests/prague/test_state_transition.py index 21789de7e8..f4f86e658f 100644 --- a/tests/prague/test_state_transition.py +++ b/tests/prague/test_state_transition.py @@ -107,8 +107,6 @@ "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", - # TODO: Current test fixtures don't support EOF along with other - # EIPs. This will be fixed in the future. )