From 872bd2e0b8cb6d3e90cd1e644d75f274e246abfa Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Mon, 11 Nov 2024 11:05:18 -0500 Subject: [PATCH 1/5] Move tx methods; add sig error (#1021) --- src/ethereum/arrow_glacier/fork.py | 269 +---------------- src/ethereum/arrow_glacier/transactions.py | 261 +++++++++++++++++ src/ethereum/berlin/fork.py | 233 +-------------- src/ethereum/berlin/transactions.py | 225 ++++++++++++++ src/ethereum/byzantium/fork.py | 184 +----------- src/ethereum/byzantium/transactions.py | 181 +++++++++++- src/ethereum/cancun/fork.py | 275 +----------------- src/ethereum/cancun/transactions.py | 271 +++++++++++++++++ src/ethereum/constantinople/fork.py | 184 +----------- src/ethereum/constantinople/transactions.py | 181 +++++++++++- src/ethereum/dao_fork/fork.py | 145 +-------- src/ethereum/dao_fork/transactions.py | 142 ++++++++- src/ethereum/exceptions.py | 6 + src/ethereum/frontier/fork.py | 137 +-------- src/ethereum/frontier/transactions.py | 137 ++++++++- src/ethereum/gray_glacier/fork.py | 269 +---------------- src/ethereum/gray_glacier/transactions.py | 261 +++++++++++++++++ src/ethereum/homestead/fork.py | 145 +-------- src/ethereum/homestead/transactions.py | 142 ++++++++- src/ethereum/istanbul/fork.py | 184 +----------- src/ethereum/istanbul/transactions.py | 181 +++++++++++- src/ethereum/london/fork.py | 269 +---------------- src/ethereum/london/transactions.py | 261 +++++++++++++++++ src/ethereum/muir_glacier/fork.py | 184 +----------- src/ethereum/muir_glacier/transactions.py | 181 +++++++++++- src/ethereum/paris/fork.py | 269 +---------------- src/ethereum/paris/transactions.py | 261 +++++++++++++++++ src/ethereum/shanghai/fork.py | 275 +----------------- src/ethereum/shanghai/transactions.py | 268 +++++++++++++++++ src/ethereum/spurious_dragon/fork.py | 184 +----------- src/ethereum/spurious_dragon/transactions.py | 181 +++++++++++- src/ethereum/tangerine_whistle/fork.py | 145 +-------- .../tangerine_whistle/transactions.py | 142 ++++++++- .../evm_tools/loaders/fork_loader.py | 12 +- src/ethereum_spec_tools/evm_tools/utils.py | 15 +- 35 files changed, 3354 insertions(+), 3306 deletions(-) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index 1cce622ecd..8b24973a19 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -866,263 +862,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - access_list_cost = 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 - - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: 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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + access_list_cost = 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 + + 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) + ) + + 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, + ) + ) + ) diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index b7764c2fad..a3bdea1a00 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,17 +38,14 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -744,227 +740,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - access_list_cost = 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 - - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 7215321be2..17c941886c 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -85,3 +89,224 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return rlp.decode_to(AccessListTransaction, tx[1:]) 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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + access_list_cost = 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 + + 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) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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, + ) + ) + ) diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index 3e311e695f..fad24adba7 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -722,180 +720,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_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. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_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. + """ + v, r, s = tx.v, 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 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) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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), + ) + ) + ) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 454ded353a..ada6431cf3 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -18,7 +18,6 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes32 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 InvalidBlock, InvalidSenderError @@ -40,19 +39,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, ) from .trie import Trie, root, trie_set from .utils.hexadecimal import hex_to_address @@ -63,7 +58,6 @@ calculate_data_fee, calculate_excess_blob_gas, calculate_total_blob_gas, - init_code_cost, ) from .vm.interpreter import MAX_CODE_SIZE, process_message_call @@ -815,271 +809,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index 13f04b3037..d1f7df04f7 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash @@ -143,3 +147,270 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +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/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 2cae7ad3fa..8f46f92056 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -722,180 +720,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_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. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_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. + """ + v, r, s = tx.v, 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 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) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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), + ) + ) + ) diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 8ebf877de1..8679efabe2 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -17,10 +17,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -41,11 +40,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -721,141 +719,6 @@ def process_transaction( return total_gas_used, output.logs -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(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. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(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. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + 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, + ) + ) + ) diff --git a/src/ethereum/exceptions.py b/src/ethereum/exceptions.py index 466778e924..8ee32d7b78 100644 --- a/src/ethereum/exceptions.py +++ b/src/ethereum/exceptions.py @@ -39,3 +39,9 @@ class InvalidSenderError(InvalidTransaction): Thrown when a transaction originates from an account that cannot send transactions. """ + + +class InvalidSignatureError(InvalidTransaction): + """ + Thrown when a transaction has an invalid signature. + """ diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 69e1cbeb21..ea04ce5719 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -18,7 +18,6 @@ from ethereum_types.bytes import Bytes, Bytes32 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.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,10 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -702,136 +701,6 @@ def process_transaction( return total_gas_used, output.logs -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - - return Uint(TX_BASE_COST + data_cost) - - -def recover_sender(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. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s >= SECP256K1N: - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index b4073223dc..e208b4cfb7 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -33,3 +38,133 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + + return Uint(TX_BASE_COST + data_cost) + + +def recover_sender(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. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s >= SECP256K1N: + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + 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, + ) + ) + ) diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index e92513a2b4..09bbc3d839 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -866,263 +862,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - access_list_cost = 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 - - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: 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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + access_list_cost = 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 + + 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) + ) + + 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, + ) + ) + ) diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index bae77156b9..8e957c7424 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,11 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -703,141 +701,6 @@ def process_transaction( return total_gas_used, output.logs -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(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. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(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. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + 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, + ) + ) + ) diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index a647f7193c..b9c6ed815c 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -723,180 +721,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_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. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index d518ac9209..b086f8c88b 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_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. + """ + v, r, s = tx.v, 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 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) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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), + ) + ) + ) diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index 2fa09d771a..f476ef5275 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -872,263 +868,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - access_list_cost = 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 - - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: 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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + access_list_cost = 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 + + 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) + ) + + 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, + ) + ) + ) diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index ba21a83202..eaf167a7fc 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +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 Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -723,180 +721,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_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. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index d518ac9209..b086f8c88b 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_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. + """ + v, r, s = tx.v, 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 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) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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), + ) + ) + ) diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index afeb620480..e6ad598a54 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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 InvalidBlock, InvalidSenderError @@ -37,18 +36,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -656,263 +652,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - access_list_cost = 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 - - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 9f1180f5c6..70df38afe2 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: 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. + """ + if calculate_intrinsic_cost(tx) > tx.gas: + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + access_list_cost = 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 + + 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) + ) + + 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, + ) + ) + ) diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index c969e3df2d..0133b64c87 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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 InvalidBlock, InvalidSenderError @@ -38,23 +37,19 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message -from .vm.gas import init_code_cost -from .vm.interpreter import MAX_CODE_SIZE, process_message_call +from .vm.interpreter import process_message_call BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) ELASTICITY_MULTIPLIER = Uint(2) @@ -680,266 +675,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -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. - """ - 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. - """ - 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)): - 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 InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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) - ) - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index 9f1180f5c6..dad2bbd956 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -10,6 +10,10 @@ 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 .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,267 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: 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)): + 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) + ) + + 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, + ) + ) + ) diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index bb1c9bbbc1..3e358272fc 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -718,180 +716,6 @@ def process_transaction( return total_gas_used, output.logs -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_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. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - 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 InvalidBlock - 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]) - - -def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_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. + """ + v, r, s = tx.v, 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 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) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> 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: Transaction, 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), + ) + ) + ) diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index bae77156b9..8e957c7424 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 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.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,11 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -703,141 +701,6 @@ def process_transaction( return total_gas_used, output.logs -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. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - 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. - """ - 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 - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(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. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - 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 compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +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 .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +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. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + 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. + """ + 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 + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(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. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + 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, + ) + ) + ) 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 2b4d4e9ca9..5132577567 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -80,32 +80,32 @@ def make_receipt(self) -> Any: @property def signing_hash(self) -> Any: """signing_hash function of the fork""" - return self._module("fork").signing_hash + return self._module("transactions").signing_hash @property def signing_hash_pre155(self) -> Any: """signing_hash_pre155 function of the fork""" - return self._module("fork").signing_hash_pre155 + return self._module("transactions").signing_hash_pre155 @property def signing_hash_155(self) -> Any: """signing_hash_155 function of the fork""" - return self._module("fork").signing_hash_155 + return self._module("transactions").signing_hash_155 @property def signing_hash_2930(self) -> Any: """signing_hash_2930 function of the fork""" - return self._module("fork").signing_hash_2930 + return self._module("transactions").signing_hash_2930 @property def signing_hash_1559(self) -> Any: """signing_hash_1559 function of the fork""" - return self._module("fork").signing_hash_1559 + return self._module("transactions").signing_hash_1559 @property def signing_hash_4844(self) -> Any: """signing_hash_4844 function of the fork""" - return self._module("fork").signing_hash_4844 + return self._module("transactions").signing_hash_4844 @property def check_transaction(self) -> Any: diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/evm_tools/utils.py index 46f6a5d253..258f9d1966 100644 --- a/src/ethereum_spec_tools/evm_tools/utils.py +++ b/src/ethereum_spec_tools/evm_tools/utils.py @@ -5,7 +5,16 @@ import json import logging import sys -from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Sequence, + Tuple, + TypeVar, +) import coincurve from ethereum_types.numeric import U64, U256, Uint @@ -82,7 +91,9 @@ def ensure_success(f: Callable, *args: Any) -> Any: raise FatalException(e) -def get_module_name(forks: Any, options: Any, stdin: Any) -> Tuple[str, int]: +def get_module_name( + forks: Sequence[Hardfork], options: Any, stdin: Any +) -> Tuple[str, int]: """ Get the module name and the fork block for the given state fork. """ From 0ffff6c63871a3ed9f55d61abb8ae8d68c19f6db Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 12 Nov 2024 10:36:02 -0500 Subject: [PATCH 2/5] Switch to ethereum rlp --- setup.cfg | 1 + src/ethereum/arrow_glacier/fork.py | 15 +- src/ethereum/arrow_glacier/fork_types.py | 2 +- src/ethereum/arrow_glacier/transactions.py | 2 +- src/ethereum/arrow_glacier/trie.py | 2 +- src/ethereum/arrow_glacier/utils/address.py | 2 +- src/ethereum/berlin/fork.py | 15 +- src/ethereum/berlin/fork_types.py | 2 +- src/ethereum/berlin/transactions.py | 2 +- src/ethereum/berlin/trie.py | 2 +- src/ethereum/berlin/utils/address.py | 2 +- src/ethereum/byzantium/fork.py | 15 +- src/ethereum/byzantium/fork_types.py | 2 +- src/ethereum/byzantium/transactions.py | 2 +- src/ethereum/byzantium/trie.py | 2 +- src/ethereum/byzantium/utils/address.py | 2 +- src/ethereum/cancun/fork.py | 2 +- src/ethereum/cancun/fork_types.py | 2 +- src/ethereum/cancun/transactions.py | 2 +- src/ethereum/cancun/trie.py | 2 +- src/ethereum/cancun/utils/address.py | 2 +- src/ethereum/constantinople/fork.py | 15 +- src/ethereum/constantinople/fork_types.py | 2 +- src/ethereum/constantinople/transactions.py | 2 +- src/ethereum/constantinople/trie.py | 2 +- src/ethereum/constantinople/utils/address.py | 2 +- src/ethereum/dao_fork/fork.py | 15 +- src/ethereum/dao_fork/fork_types.py | 2 +- src/ethereum/dao_fork/transactions.py | 2 +- src/ethereum/dao_fork/trie.py | 2 +- src/ethereum/dao_fork/utils/address.py | 2 +- src/ethereum/ethash.py | 6 +- src/ethereum/exceptions.py | 12 - src/ethereum/frontier/fork.py | 15 +- src/ethereum/frontier/fork_types.py | 2 +- src/ethereum/frontier/transactions.py | 2 +- src/ethereum/frontier/trie.py | 2 +- src/ethereum/frontier/utils/address.py | 2 +- src/ethereum/genesis.py | 6 +- src/ethereum/gray_glacier/fork.py | 15 +- src/ethereum/gray_glacier/fork_types.py | 2 +- src/ethereum/gray_glacier/transactions.py | 2 +- src/ethereum/gray_glacier/trie.py | 2 +- src/ethereum/gray_glacier/utils/address.py | 2 +- src/ethereum/homestead/fork.py | 15 +- src/ethereum/homestead/fork_types.py | 2 +- src/ethereum/homestead/transactions.py | 2 +- src/ethereum/homestead/trie.py | 2 +- src/ethereum/homestead/utils/address.py | 2 +- src/ethereum/istanbul/fork.py | 15 +- src/ethereum/istanbul/fork_types.py | 2 +- src/ethereum/istanbul/transactions.py | 2 +- src/ethereum/istanbul/trie.py | 2 +- src/ethereum/istanbul/utils/address.py | 2 +- src/ethereum/london/fork.py | 15 +- src/ethereum/london/fork_types.py | 2 +- src/ethereum/london/transactions.py | 2 +- src/ethereum/london/trie.py | 2 +- src/ethereum/london/utils/address.py | 2 +- src/ethereum/muir_glacier/fork.py | 15 +- src/ethereum/muir_glacier/fork_types.py | 2 +- src/ethereum/muir_glacier/transactions.py | 2 +- src/ethereum/muir_glacier/trie.py | 2 +- src/ethereum/muir_glacier/utils/address.py | 2 +- src/ethereum/paris/fork.py | 2 +- src/ethereum/paris/fork_types.py | 2 +- src/ethereum/paris/transactions.py | 2 +- src/ethereum/paris/trie.py | 2 +- src/ethereum/paris/utils/address.py | 2 +- src/ethereum/rlp.py | 573 ------------------ src/ethereum/shanghai/fork.py | 2 +- src/ethereum/shanghai/fork_types.py | 2 +- src/ethereum/shanghai/transactions.py | 2 +- src/ethereum/shanghai/trie.py | 2 +- src/ethereum/shanghai/utils/address.py | 2 +- src/ethereum/spurious_dragon/fork.py | 15 +- src/ethereum/spurious_dragon/fork_types.py | 2 +- src/ethereum/spurious_dragon/transactions.py | 2 +- src/ethereum/spurious_dragon/trie.py | 2 +- src/ethereum/spurious_dragon/utils/address.py | 2 +- src/ethereum/tangerine_whistle/fork.py | 15 +- src/ethereum/tangerine_whistle/fork_types.py | 2 +- .../tangerine_whistle/transactions.py | 2 +- src/ethereum/tangerine_whistle/trie.py | 2 +- .../tangerine_whistle/utils/address.py | 2 +- .../evm_tools/b11r/__init__.py | 5 +- .../evm_tools/b11r/b11r_types.py | 2 +- .../evm_tools/loaders/fixture_loader.py | 6 +- .../evm_tools/loaders/transaction_loader.py | 2 +- .../evm_tools/t8n/__init__.py | 3 +- src/ethereum_spec_tools/evm_tools/t8n/env.py | 2 +- .../evm_tools/t8n/t8n_types.py | 2 +- src/ethereum_spec_tools/sync.py | 3 +- tests/berlin/test_ethash.py | 2 +- tests/berlin/test_rlp.py | 2 +- tests/berlin/test_state_transition.py | 6 +- tests/berlin/test_transaction.py | 2 +- tests/byzantium/test_ethash.py | 2 +- tests/byzantium/test_rlp.py | 2 +- tests/byzantium/test_state_transition.py | 6 +- tests/byzantium/test_transaction.py | 2 +- tests/cancun/test_rlp.py | 2 +- tests/constantinople/test_ethash.py | 2 +- tests/constantinople/test_rlp.py | 2 +- tests/constantinople/test_state_transition.py | 6 +- tests/constantinople/test_transaction.py | 2 +- tests/frontier/test_ethash.py | 2 +- tests/frontier/test_rlp.py | 2 +- tests/frontier/test_state_transition.py | 6 +- tests/frontier/test_transaction.py | 2 +- tests/helpers/load_state_tests.py | 12 +- tests/helpers/load_vm_tests.py | 2 +- tests/homestead/test_ethash.py | 2 +- tests/homestead/test_rlp.py | 2 +- tests/homestead/test_state_transition.py | 6 +- tests/homestead/test_transaction.py | 2 +- tests/istanbul/test_ethash.py | 2 +- tests/istanbul/test_rlp.py | 2 +- tests/istanbul/test_state_transition.py | 6 +- tests/istanbul/test_transaction.py | 2 +- tests/london/test_rlp.py | 2 +- tests/london/test_state_transition.py | 6 +- tests/london/test_transaction.py | 2 +- tests/paris/test_rlp.py | 2 +- tests/paris/test_state_transition.py | 6 +- tests/shanghai/test_rlp.py | 2 +- tests/spurious_dragon/test_ethash.py | 2 +- tests/spurious_dragon/test_rlp.py | 2 +- .../spurious_dragon/test_state_transition.py | 6 +- tests/spurious_dragon/test_transaction.py | 2 +- tests/tangerine_whistle/test_ethash.py | 2 +- tests/tangerine_whistle/test_rlp.py | 2 +- .../test_state_transition.py | 6 +- tests/tangerine_whistle/test_transaction.py | 2 +- tests/test_genesis.py | 2 +- tests/test_rlp.py | 337 +--------- 136 files changed, 263 insertions(+), 1160 deletions(-) delete mode 100644 src/ethereum/rlp.py diff --git a/setup.cfg b/setup.cfg index 730939c928..bee1da28ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -116,6 +116,7 @@ install_requires = typing_extensions>=4 py_ecc @ git+https://github.com/petertdavies/py_ecc.git@127184f4c57b1812da959586d0fe8f43bb1a2389 ethereum-types>=0.2.1,<0.3 + ethereum-rlp>=0.1.1,<0.2 [options.package_data] ethereum = diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index 8b24973a19..2108a5fba7 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -351,7 +351,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.base_fee_per_gas, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -650,8 +650,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -669,18 +669,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/arrow_glacier/fork_types.py b/src/ethereum/arrow_glacier/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/arrow_glacier/fork_types.py +++ b/src/ethereum/arrow_glacier/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index 10f52ad908..9d9bb6bd17 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/arrow_glacier/trie.py b/src/ethereum/arrow_glacier/trie.py index 069b8127e2..476a61ab8f 100644 --- a/src/ethereum/arrow_glacier/trie.py +++ b/src/ethereum/arrow_glacier/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.london import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/arrow_glacier/utils/address.py b/src/ethereum/arrow_glacier/utils/address.py index 7bd7f1e1bf..a19077d4ea 100644 --- a/src/ethereum/arrow_glacier/utils/address.py +++ b/src/ethereum/arrow_glacier/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index a3bdea1a00..8ec009f424 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -274,7 +274,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -544,8 +544,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -563,18 +563,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/berlin/fork_types.py b/src/ethereum/berlin/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/berlin/fork_types.py +++ b/src/ethereum/berlin/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 17c941886c..fcc54d0485 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/berlin/trie.py b/src/ethereum/berlin/trie.py index 5ab348f521..74068d6331 100644 --- a/src/ethereum/berlin/trie.py +++ b/src/ethereum/berlin/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.muir_glacier import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/berlin/utils/address.py b/src/ethereum/berlin/utils/address.py index e1c01739ca..c77bd34d99 100644 --- a/src/ethereum/berlin/utils/address.py +++ b/src/ethereum/berlin/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index fad24adba7..68001e3ee1 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -15,6 +15,7 @@ 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 @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -270,7 +270,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -534,8 +534,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -553,18 +553,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/byzantium/fork_types.py b/src/ethereum/byzantium/fork_types.py index fbf239a3f6..328a2779ee 100644 --- a/src/ethereum/byzantium/fork_types.py +++ b/src/ethereum/byzantium/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index da6aff2c1b..142929587d 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/byzantium/trie.py b/src/ethereum/byzantium/trie.py index 9d2a8f256e..a8e2890907 100644 --- a/src/ethereum/byzantium/trie.py +++ b/src/ethereum/byzantium/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.spurious_dragon import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/byzantium/utils/address.py b/src/ethereum/byzantium/utils/address.py index f175816214..386c13bff2 100644 --- a/src/ethereum/byzantium/utils/address.py +++ b/src/ethereum/byzantium/utils/address.py @@ -14,12 +14,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index ada6431cf3..531de2ed5a 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -15,13 +15,13 @@ from dataclasses import dataclass from typing import List, Optional, Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal from .bloom import logs_bloom diff --git a/src/ethereum/cancun/fork_types.py b/src/ethereum/cancun/fork_types.py index c53dcb62d3..dc287c84f1 100644 --- a/src/ethereum/cancun/fork_types.py +++ b/src/ethereum/cancun/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index d1f7df04f7..3fe7a584f5 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash diff --git a/src/ethereum/cancun/trie.py b/src/ethereum/cancun/trie.py index db0394cdb2..082cbb5495 100644 --- a/src/ethereum/cancun/trie.py +++ b/src/ethereum/cancun/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.shanghai import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt, Withdrawal from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/cancun/utils/address.py b/src/ethereum/cancun/utils/address.py index 2ff9ea38cf..1bfbd2326c 100644 --- a/src/ethereum/cancun/utils/address.py +++ b/src/ethereum/cancun/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 8f46f92056..e195589a13 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -15,6 +15,7 @@ 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 @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -270,7 +270,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -534,8 +534,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -553,18 +553,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/constantinople/fork_types.py b/src/ethereum/constantinople/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/constantinople/fork_types.py +++ b/src/ethereum/constantinople/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index da6aff2c1b..142929587d 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/constantinople/trie.py b/src/ethereum/constantinople/trie.py index 6d61f3aaa9..9e9d3052e9 100644 --- a/src/ethereum/constantinople/trie.py +++ b/src/ethereum/constantinople/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/constantinople/utils/address.py b/src/ethereum/constantinople/utils/address.py index 709f8b6ab8..96c72cbc85 100644 --- a/src/ethereum/constantinople/utils/address.py +++ b/src/ethereum/constantinople/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 8679efabe2..1835a2525f 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -17,6 +17,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint @@ -24,7 +25,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import FORK_CRITERIA, vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -282,7 +282,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -540,8 +540,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -559,18 +559,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/dao_fork/fork_types.py b/src/ethereum/dao_fork/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/dao_fork/fork_types.py +++ b/src/ethereum/dao_fork/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index a9caf4990f..57d697d615 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/dao_fork/trie.py b/src/ethereum/dao_fork/trie.py index 0b1bd95d96..f5577adffd 100644 --- a/src/ethereum/dao_fork/trie.py +++ b/src/ethereum/dao_fork/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.homestead import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/dao_fork/utils/address.py b/src/ethereum/dao_fork/utils/address.py index 83fea9e2b4..f9434e1dd0 100644 --- a/src/ethereum/dao_fork/utils/address.py +++ b/src/ethereum/dao_fork/utils/address.py @@ -13,12 +13,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum/ethash.py b/src/ethereum/ethash.py index 5dcd2f766b..6cb9aca590 100644 --- a/src/ethereum/ethash.py +++ b/src/ethereum/ethash.py @@ -349,7 +349,7 @@ def hashimoto( #### Parameters - - `header_hash` is a valid [RLP hash] of a block header. + - `header_hash` is a valid RLP hash of a block header. - `nonce` is the propagated nonce for the given block. - `dataset_size` is the size of the dataset. See [`dataset_size`]. - `fetch_dataset_item` is a function that retrieves a specific dataset item @@ -361,7 +361,6 @@ def hashimoto( - The final result obtained which will be checked for leading zeros (in byte representation) in correspondence with the block difficulty. - [RLP hash]: ref:ethereum.rlp.rlp_hash [`dataset_size`]: ref:ethereum.ethash.dataset_size """ nonce_le = bytes(reversed(nonce)) @@ -405,7 +404,7 @@ def hashimoto_light( #### Parameters - - `header_hash` is a valid [RLP hash] of a block header. + - `header_hash` is a valid RLP hash of a block header. - `nonce` is the propagated nonce for the given block. - `cache` is the cache generated by [`generate_cache`]. - `dataset_size` is the size of the dataset. See [`dataset_size`]. @@ -416,7 +415,6 @@ def hashimoto_light( - The final result obtained which will be checked for leading zeros (in byte representation) in correspondence with the block difficulty. - [RLP hash]: ref:ethereum.rlp.rlp_hash [`dataset_size`]: ref:ethereum.ethash.dataset_size [`generate_cache`]: ref:ethereum.ethash.generate_cache [`hashimoto`]: ref:ethereum.ethash.hashimoto diff --git a/src/ethereum/exceptions.py b/src/ethereum/exceptions.py index 8ee32d7b78..7fd959d9d3 100644 --- a/src/ethereum/exceptions.py +++ b/src/ethereum/exceptions.py @@ -22,18 +22,6 @@ class InvalidTransaction(EthereumException): """ -class RLPDecodingError(EthereumException): - """ - Indicates that RLP decoding failed. - """ - - -class RLPEncodingError(EthereumException): - """ - Indicates that RLP encoding failed. - """ - - class InvalidSenderError(InvalidTransaction): """ Thrown when a transaction originates from an account that cannot send diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index ea04ce5719..46749732f4 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -264,7 +264,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -522,8 +522,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -541,18 +541,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/frontier/fork_types.py b/src/ethereum/frontier/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/frontier/fork_types.py +++ b/src/ethereum/frontier/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index e208b4cfb7..a09bba38ba 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/frontier/trie.py b/src/ethereum/frontier/trie.py index 587e811172..1f20ad5af1 100644 --- a/src/ethereum/frontier/trie.py +++ b/src/ethereum/frontier/trie.py @@ -28,6 +28,7 @@ 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 @@ -35,7 +36,6 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/frontier/utils/address.py b/src/ethereum/frontier/utils/address.py index ed1f792cd6..aee29af13d 100644 --- a/src/ethereum/frontier/utils/address.py +++ b/src/ethereum/frontier/utils/address.py @@ -13,12 +13,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum/genesis.py b/src/ethereum/genesis.py index a7b515b309..330022bd40 100644 --- a/src/ethereum/genesis.py +++ b/src/ethereum/genesis.py @@ -17,12 +17,12 @@ from dataclasses import dataclass from typing import Any, Callable, Dict, Generic, Type, TypeVar +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32, FixedBytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.utils import has_field from ethereum.utils.hexadecimal import ( hex_to_bytes, @@ -220,7 +220,7 @@ def add_genesis_block( fields = { "parent_hash": Hash32(b"\0" * 32), - "ommers_hash": rlp.rlp_hash(()), + "ommers_hash": keccak256(rlp.encode(())), "coinbase": Address(b"\0" * Address.LENGTH), "state_root": hardfork.state_root(chain.state), "transactions_root": hardfork.root(hardfork.Trie(False, None)), diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 09bbc3d839..ce070e7c08 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -351,7 +351,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.base_fee_per_gas, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -650,8 +650,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -669,18 +669,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/gray_glacier/fork_types.py b/src/ethereum/gray_glacier/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/gray_glacier/fork_types.py +++ b/src/ethereum/gray_glacier/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index 10f52ad908..9d9bb6bd17 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/gray_glacier/trie.py b/src/ethereum/gray_glacier/trie.py index e6ee3e9bef..45b96d4226 100644 --- a/src/ethereum/gray_glacier/trie.py +++ b/src/ethereum/gray_glacier/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/gray_glacier/utils/address.py b/src/ethereum/gray_glacier/utils/address.py index 962bb0a1fa..35b219756c 100644 --- a/src/ethereum/gray_glacier/utils/address.py +++ b/src/ethereum/gray_glacier/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index 8e957c7424..1419c87b6d 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -264,7 +264,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -522,8 +522,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -541,18 +541,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/homestead/fork_types.py b/src/ethereum/homestead/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/homestead/fork_types.py +++ b/src/ethereum/homestead/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index a9caf4990f..57d697d615 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/homestead/trie.py b/src/ethereum/homestead/trie.py index 5294a2ba0d..96efb4d8d4 100644 --- a/src/ethereum/homestead/trie.py +++ b/src/ethereum/homestead/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.frontier import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/homestead/utils/address.py b/src/ethereum/homestead/utils/address.py index febe47f5c6..a545a94d91 100644 --- a/src/ethereum/homestead/utils/address.py +++ b/src/ethereum/homestead/utils/address.py @@ -13,12 +13,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index b9c6ed815c..35eb37c9c5 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -15,6 +15,7 @@ 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 @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -270,7 +270,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -535,8 +535,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -554,18 +554,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/istanbul/fork_types.py b/src/ethereum/istanbul/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/istanbul/fork_types.py +++ b/src/ethereum/istanbul/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index b086f8c88b..2bdb3603b9 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/istanbul/trie.py b/src/ethereum/istanbul/trie.py index a1a4cfbf98..64bd03ecb6 100644 --- a/src/ethereum/istanbul/trie.py +++ b/src/ethereum/istanbul/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/istanbul/utils/address.py b/src/ethereum/istanbul/utils/address.py index 7429e4a7e4..8501d2066c 100644 --- a/src/ethereum/istanbul/utils/address.py +++ b/src/ethereum/istanbul/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index f476ef5275..a9599345c9 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import FORK_CRITERIA, vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -357,7 +357,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.base_fee_per_gas, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -656,8 +656,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -675,18 +675,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/london/fork_types.py b/src/ethereum/london/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/london/fork_types.py +++ b/src/ethereum/london/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index 10f52ad908..9d9bb6bd17 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/london/trie.py b/src/ethereum/london/trie.py index 03e230531c..71b83f7921 100644 --- a/src/ethereum/london/trie.py +++ b/src/ethereum/london/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/london/utils/address.py b/src/ethereum/london/utils/address.py index 0461f13c20..54c000069e 100644 --- a/src/ethereum/london/utils/address.py +++ b/src/ethereum/london/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index eaf167a7fc..4060049c84 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -15,6 +15,7 @@ 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 @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -270,7 +270,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -535,8 +535,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -554,18 +554,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/muir_glacier/fork_types.py b/src/ethereum/muir_glacier/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/muir_glacier/fork_types.py +++ b/src/ethereum/muir_glacier/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index b086f8c88b..2bdb3603b9 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/muir_glacier/trie.py b/src/ethereum/muir_glacier/trie.py index f2e4f29460..ab1a5fd3fe 100644 --- a/src/ethereum/muir_glacier/trie.py +++ b/src/ethereum/muir_glacier/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.istanbul import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/muir_glacier/utils/address.py b/src/ethereum/muir_glacier/utils/address.py index 72b5c4cd06..5e6515f8b1 100644 --- a/src/ethereum/muir_glacier/utils/address.py +++ b/src/ethereum/muir_glacier/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index e6ad598a54..1e34b33159 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -15,13 +15,13 @@ 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 rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom diff --git a/src/ethereum/paris/fork_types.py b/src/ethereum/paris/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/paris/fork_types.py +++ b/src/ethereum/paris/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 70df38afe2..5944d96b6e 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/paris/trie.py b/src/ethereum/paris/trie.py index ccd99a17af..f51dba170d 100644 --- a/src/ethereum/paris/trie.py +++ b/src/ethereum/paris/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.gray_glacier import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/paris/utils/address.py b/src/ethereum/paris/utils/address.py index 7d5933207f..1f26a8f723 100644 --- a/src/ethereum/paris/utils/address.py +++ b/src/ethereum/paris/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/rlp.py b/src/ethereum/rlp.py deleted file mode 100644 index e1fd7165ad..0000000000 --- a/src/ethereum/rlp.py +++ /dev/null @@ -1,573 +0,0 @@ -""" -.. _rlp: - -Recursive Length Prefix (RLP) Encoding -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Defines the serialization and deserialization format used throughout Ethereum. -""" - -from dataclasses import astuple, fields, is_dataclass -from typing import ( - Any, - ClassVar, - Dict, - Protocol, - Sequence, - Tuple, - Type, - TypeAlias, - TypeVar, - Union, - cast, - get_args, - get_origin, - get_type_hints, - overload, -) - -from ethereum_types.bytes import Bytes, FixedBytes -from ethereum_types.numeric import FixedUnsigned, Uint - -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.exceptions import RLPDecodingError, RLPEncodingError - - -class RLP(Protocol): - """ - [`Protocol`] that describes the requirements to be RLP-encodable. - - [`Protocol`]: https://docs.python.org/3/library/typing.html#typing.Protocol - """ - - __dataclass_fields__: ClassVar[Dict] - - -Simple: TypeAlias = Union[Sequence["Simple"], bytes] - -Extended: TypeAlias = Union[ - Sequence["Extended"], bytearray, bytes, Uint, FixedUnsigned, str, bool, RLP -] - - -# -# RLP Encode -# - - -def encode(raw_data: Extended) -> Bytes: - """ - Encodes `raw_data` into a sequence of bytes using RLP. - - Parameters - ---------- - raw_data : - A `Bytes`, `Uint`, `Uint256` or sequence of `RLP` encodable - objects. - - Returns - ------- - encoded : `ethereum.base_types.Bytes` - The RLP encoded bytes representing `raw_data`. - """ - if isinstance(raw_data, Sequence): - if isinstance(raw_data, (bytearray, bytes)): - return encode_bytes(raw_data) - elif isinstance(raw_data, str): - return encode_bytes(raw_data.encode()) - else: - return encode_sequence(raw_data) - elif isinstance(raw_data, (Uint, FixedUnsigned)): - return encode(raw_data.to_be_bytes()) - elif isinstance(raw_data, bool): - if raw_data: - return encode_bytes(b"\x01") - else: - return encode_bytes(b"") - elif is_dataclass(raw_data): - return encode(astuple(raw_data)) - else: - raise RLPEncodingError( - "RLP Encoding of type {} is not supported".format(type(raw_data)) - ) - - -def encode_bytes(raw_bytes: Bytes) -> Bytes: - """ - Encodes `raw_bytes`, a sequence of bytes, using RLP. - - Parameters - ---------- - raw_bytes : - Bytes to encode with RLP. - - Returns - ------- - encoded : `ethereum.base_types.Bytes` - The RLP encoded bytes representing `raw_bytes`. - """ - len_raw_data = len(raw_bytes) - - if len_raw_data == 1 and raw_bytes[0] < 0x80: - return raw_bytes - elif len_raw_data < 0x38: - return bytes([0x80 + len_raw_data]) + raw_bytes - else: - # length of raw data represented as big endian bytes - len_raw_data_as_be = Uint(len_raw_data).to_be_bytes() - return ( - bytes([0xB7 + len(len_raw_data_as_be)]) - + len_raw_data_as_be - + raw_bytes - ) - - -def encode_sequence(raw_sequence: Sequence[Extended]) -> Bytes: - """ - Encodes a list of RLP encodable objects (`raw_sequence`) using RLP. - - Parameters - ---------- - raw_sequence : - Sequence of RLP encodable objects. - - Returns - ------- - encoded : `ethereum.base_types.Bytes` - The RLP encoded bytes representing `raw_sequence`. - """ - joined_encodings = get_joined_encodings(raw_sequence) - len_joined_encodings = len(joined_encodings) - - if len_joined_encodings < 0x38: - return Bytes([0xC0 + len_joined_encodings]) + joined_encodings - else: - len_joined_encodings_as_be = Uint(len_joined_encodings).to_be_bytes() - return ( - Bytes([0xF7 + len(len_joined_encodings_as_be)]) - + len_joined_encodings_as_be - + joined_encodings - ) - - -def get_joined_encodings(raw_sequence: Sequence[Extended]) -> Bytes: - """ - Obtain concatenation of rlp encoding for each item in the sequence - raw_sequence. - - Parameters - ---------- - raw_sequence : - Sequence to encode with RLP. - - Returns - ------- - joined_encodings : `ethereum.base_types.Bytes` - The concatenated RLP encoded bytes for each item in sequence - raw_sequence. - """ - return b"".join(encode(item) for item in raw_sequence) - - -# -# RLP Decode -# - - -def decode(encoded_data: Bytes) -> Simple: - """ - Decodes an integer, byte sequence, or list of RLP encodable objects - from the byte sequence `encoded_data`, using RLP. - - Parameters - ---------- - encoded_data : - A sequence of bytes, in RLP form. - - Returns - ------- - decoded_data : `RLP` - Object decoded from `encoded_data`. - """ - if len(encoded_data) <= 0: - raise RLPDecodingError("Cannot decode empty bytestring") - - if encoded_data[0] <= 0xBF: - # This means that the raw data is of type bytes - return decode_to_bytes(encoded_data) - else: - # This means that the raw data is of type sequence - return decode_to_sequence(encoded_data) - - -U = TypeVar("U", bound=Extended) - - -def decode_to(cls: Type[U], encoded_data: Bytes) -> U: - """ - Decode the bytes in `encoded_data` to an object of type `cls`. `cls` can be - a `Bytes` subclass, a dataclass, `Uint`, `U256` or `Tuple[cls]`. - - Parameters - ---------- - cls: `Type[U]` - The type to decode to. - encoded_data : - A sequence of bytes, in RLP form. - - Returns - ------- - decoded_data : `U` - Object decoded from `encoded_data`. - """ - decoded = decode(encoded_data) - return _deserialize_to(cls, decoded) - - -@overload -def _deserialize_to(class_: Type[U], value: Simple) -> U: - pass - - -@overload -def _deserialize_to(class_: object, value: Simple) -> Extended: - pass - - -def _deserialize_to(class_: object, value: Simple) -> Extended: - if not isinstance(class_, type): - return _deserialize_to_annotation(class_, value) - elif is_dataclass(class_): - return _deserialize_to_dataclass(class_, value) - elif issubclass(class_, (Uint, FixedUnsigned)): - return _deserialize_to_uint(class_, value) - elif issubclass(class_, (Bytes, FixedBytes)): - return _deserialize_to_bytes(class_, value) - elif class_ is bool: - return _deserialize_to_bool(value) - else: - raise NotImplementedError(class_) - - -def _deserialize_to_dataclass(cls: Type[U], decoded: Simple) -> U: - assert is_dataclass(cls) - hints = get_type_hints(cls) - target_fields = fields(cls) - - if isinstance(decoded, bytes): - raise RLPDecodingError(f"got `bytes` while decoding `{cls.__name__}`") - - if len(target_fields) != len(decoded): - name = cls.__name__ - actual = len(decoded) - expected = len(target_fields) - raise RLPDecodingError( - f"`{name}` needs {expected} field(s), but got {actual} instead" - ) - - values: Dict[str, Any] = {} - - for value, target_field in zip(decoded, target_fields): - resolved_type = hints[target_field.name] - values[target_field.name] = _deserialize_to(resolved_type, value) - - result = cls(**values) - assert isinstance(result, cls) - return cast(U, result) - - -def _deserialize_to_bool(value: Simple) -> bool: - if value == b"": - return False - elif value == b"\x01": - return True - else: - raise RLPDecodingError - - -def _deserialize_to_bytes( - class_: Union[Type[Bytes], Type[FixedBytes]], value: Simple -) -> Union[Bytes, FixedBytes]: - if not isinstance(value, bytes): - raise RLPDecodingError - try: - return class_(value) - except ValueError as e: - raise RLPDecodingError from e - - -def _deserialize_to_uint( - class_: Union[Type[Uint], Type[FixedUnsigned]], decoded: Simple -) -> Union[Uint, FixedUnsigned]: - if not isinstance(decoded, bytes): - raise RLPDecodingError - try: - return class_.from_be_bytes(decoded) - except ValueError as e: - raise RLPDecodingError from e - - -def _deserialize_to_annotation(annotation: object, value: Simple) -> Extended: - origin = get_origin(annotation) - if origin is Union: - return _deserialize_to_union(annotation, value) - elif origin in (Tuple, tuple): - return _deserialize_to_tuple(annotation, value) - elif origin is None: - raise Exception(annotation) - else: - raise NotImplementedError(f"RLP non-type {origin!r}") - - -def _deserialize_to_union(annotation: object, value: Simple) -> Extended: - arguments = get_args(annotation) - successes = [] - failures = [] - for argument in arguments: - try: - success = _deserialize_to(argument, value) - except Exception as e: - failures.append(e) - continue - - successes.append(success) - - if len(successes) == 1: - return successes[0] - elif not successes: - raise RLPDecodingError(f"no matching union variant\n{failures!r}") - else: - raise RLPDecodingError("multiple matching union variants") - - -def _deserialize_to_tuple( - annotation: object, values: Simple -) -> Sequence[Extended]: - if isinstance(values, bytes): - raise RLPDecodingError - arguments = list(get_args(annotation)) - - if arguments[-1] is Ellipsis: - arguments.pop() - fill_count = len(values) - len(arguments) - arguments = list(arguments) + [arguments[-1]] * fill_count - - decoded = [] - for argument, value in zip(arguments, values): - decoded.append(_deserialize_to(argument, value)) - - return tuple(decoded) - - -def decode_to_bytes(encoded_bytes: Bytes) -> Bytes: - """ - Decodes a rlp encoded byte stream assuming that the decoded data - should be of type `bytes`. - - Parameters - ---------- - encoded_bytes : - RLP encoded byte stream. - - Returns - ------- - decoded : `ethereum.base_types.Bytes` - RLP decoded Bytes data - """ - if len(encoded_bytes) == 1 and encoded_bytes[0] < 0x80: - return encoded_bytes - elif encoded_bytes[0] <= 0xB7: - len_raw_data = encoded_bytes[0] - 0x80 - if len_raw_data >= len(encoded_bytes): - raise RLPDecodingError - raw_data = encoded_bytes[1 : 1 + len_raw_data] - if len_raw_data == 1 and raw_data[0] < 0x80: - raise RLPDecodingError - return raw_data - else: - # This is the index in the encoded data at which decoded data - # starts from. - decoded_data_start_idx = 1 + encoded_bytes[0] - 0xB7 - if decoded_data_start_idx - 1 >= len(encoded_bytes): - raise RLPDecodingError - if encoded_bytes[1] == 0: - raise RLPDecodingError - len_decoded_data = int( - Uint.from_be_bytes(encoded_bytes[1:decoded_data_start_idx]) - ) - if len_decoded_data < 0x38: - raise RLPDecodingError - decoded_data_end_idx = decoded_data_start_idx + int(len_decoded_data) - if decoded_data_end_idx - 1 >= len(encoded_bytes): - raise RLPDecodingError - return encoded_bytes[decoded_data_start_idx:decoded_data_end_idx] - - -def decode_to_sequence(encoded_sequence: Bytes) -> Sequence[Simple]: - """ - Decodes a rlp encoded byte stream assuming that the decoded data - should be of type `Sequence` of objects. - - Parameters - ---------- - encoded_sequence : - An RLP encoded Sequence. - - Returns - ------- - decoded : `Sequence[RLP]` - Sequence of objects decoded from `encoded_sequence`. - """ - if encoded_sequence[0] <= 0xF7: - len_joined_encodings = encoded_sequence[0] - 0xC0 - if len_joined_encodings >= len(encoded_sequence): - raise RLPDecodingError - joined_encodings = encoded_sequence[1 : 1 + len_joined_encodings] - else: - joined_encodings_start_idx = 1 + encoded_sequence[0] - 0xF7 - if joined_encodings_start_idx - 1 >= len(encoded_sequence): - raise RLPDecodingError - if encoded_sequence[1] == 0: - raise RLPDecodingError - len_joined_encodings = int( - Uint.from_be_bytes(encoded_sequence[1:joined_encodings_start_idx]) - ) - if len_joined_encodings < 0x38: - raise RLPDecodingError - joined_encodings_end_idx = ( - joined_encodings_start_idx + len_joined_encodings - ) - if joined_encodings_end_idx - 1 >= len(encoded_sequence): - raise RLPDecodingError - joined_encodings = encoded_sequence[ - joined_encodings_start_idx:joined_encodings_end_idx - ] - - return decode_joined_encodings(joined_encodings) - - -def decode_joined_encodings(joined_encodings: Bytes) -> Sequence[Simple]: - """ - Decodes `joined_encodings`, which is a concatenation of RLP encoded - objects. - - Parameters - ---------- - joined_encodings : - concatenation of RLP encoded objects - - Returns - ------- - decoded : `List[RLP]` - A list of objects decoded from `joined_encodings`. - """ - decoded_sequence = [] - - item_start_idx = 0 - while item_start_idx < len(joined_encodings): - encoded_item_length = decode_item_length( - joined_encodings[item_start_idx:] - ) - if item_start_idx + encoded_item_length - 1 >= len(joined_encodings): - raise RLPDecodingError - encoded_item = joined_encodings[ - item_start_idx : item_start_idx + encoded_item_length - ] - decoded_sequence.append(decode(encoded_item)) - item_start_idx += encoded_item_length - - return decoded_sequence - - -def decode_item_length(encoded_data: Bytes) -> int: - """ - Find the length of the rlp encoding for the first object in the - encoded sequence. - Here `encoded_data` refers to concatenation of rlp encoding for each - item in a sequence. - - NOTE - This is a helper function not described in the spec. It was - introduced as the spec doesn't discuss about decoding the RLP encoded - data. - - Parameters - ---------- - encoded_data : - RLP encoded data for a sequence of objects. - - Returns - ------- - rlp_length : `int` - """ - if len(encoded_data) <= 0: - raise RLPDecodingError - - first_rlp_byte = encoded_data[0] - - # This is the length of the big endian representation of the length of - # rlp encoded object byte stream. - length_length = 0 - decoded_data_length = 0 - - # This occurs only when the raw_data is a single byte whose value < 128 - if first_rlp_byte < 0x80: - # We return 1 here, as the end formula - # 1 + length_length + decoded_data_length would be invalid for - # this case. - return 1 - # This occurs only when the raw_data is a byte stream with length < 56 - # and doesn't fall into the above cases - elif first_rlp_byte <= 0xB7: - decoded_data_length = first_rlp_byte - 0x80 - # This occurs only when the raw_data is a byte stream and doesn't fall - # into the above cases - elif first_rlp_byte <= 0xBF: - length_length = first_rlp_byte - 0xB7 - if length_length >= len(encoded_data): - raise RLPDecodingError - if encoded_data[1] == 0: - raise RLPDecodingError - decoded_data_length = int( - Uint.from_be_bytes(encoded_data[1 : 1 + length_length]) - ) - # This occurs only when the raw_data is a sequence of objects with - # length(concatenation of encoding of each object) < 56 - elif first_rlp_byte <= 0xF7: - decoded_data_length = first_rlp_byte - 0xC0 - # This occurs only when the raw_data is a sequence of objects and - # doesn't fall into the above cases. - elif first_rlp_byte <= 0xFF: - length_length = first_rlp_byte - 0xF7 - if length_length >= len(encoded_data): - raise RLPDecodingError - if encoded_data[1] == 0: - raise RLPDecodingError - decoded_data_length = int( - Uint.from_be_bytes(encoded_data[1 : 1 + length_length]) - ) - - return 1 + length_length + decoded_data_length - - -def rlp_hash(data: Extended) -> Hash32: - """ - Obtain the keccak-256 hash of the rlp encoding of the passed in data. - - Parameters - ---------- - data : - The data for which we need the rlp hash. - - Returns - ------- - hash : `Hash32` - The rlp hash of the passed in data. - """ - return keccak256(encode(data)) diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index 0133b64c87..d0347b7d1e 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -15,13 +15,13 @@ 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 rlp from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal from .bloom import logs_bloom diff --git a/src/ethereum/shanghai/fork_types.py b/src/ethereum/shanghai/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/shanghai/fork_types.py +++ b/src/ethereum/shanghai/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index dad2bbd956..efd3b2fcda 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -6,6 +6,7 @@ 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 @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address diff --git a/src/ethereum/shanghai/trie.py b/src/ethereum/shanghai/trie.py index 020e9900c4..6a1d563593 100644 --- a/src/ethereum/shanghai/trie.py +++ b/src/ethereum/shanghai/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.paris import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt, Withdrawal from .fork_types import Account, Address, Root, encode_account from .transactions import LegacyTransaction diff --git a/src/ethereum/shanghai/utils/address.py b/src/ethereum/shanghai/utils/address.py index 96f1949a05..57d049acc2 100644 --- a/src/ethereum/shanghai/utils/address.py +++ b/src/ethereum/shanghai/utils/address.py @@ -14,13 +14,13 @@ """ 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 ... import rlp from ..fork_types import Address diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index 3e358272fc..cb4d0b0702 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -266,7 +266,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -530,8 +530,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -549,18 +549,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/spurious_dragon/fork_types.py b/src/ethereum/spurious_dragon/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/spurious_dragon/fork_types.py +++ b/src/ethereum/spurious_dragon/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index da6aff2c1b..142929587d 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/spurious_dragon/trie.py b/src/ethereum/spurious_dragon/trie.py index 1b13975d22..50e3e69c1f 100644 --- a/src/ethereum/spurious_dragon/trie.py +++ b/src/ethereum/spurious_dragon/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.tangerine_whistle import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/spurious_dragon/utils/address.py b/src/ethereum/spurious_dragon/utils/address.py index 56f2108cda..8f8c9ea503 100644 --- a/src/ethereum/spurious_dragon/utils/address.py +++ b/src/ethereum/spurious_dragon/utils/address.py @@ -14,12 +14,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index 8e957c7424..1419c87b6d 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -15,6 +15,7 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint @@ -22,7 +23,6 @@ from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from .. import rlp from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom @@ -264,7 +264,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: header.extra_data, ) - return rlp.rlp_hash(header_data_without_pow_artefacts) + return keccak256(rlp.encode(header_data_without_pow_artefacts)) def validate_proof_of_work(header: Header) -> None: @@ -522,8 +522,8 @@ def validate_ommers( chain : History and current state. """ - block_hash = rlp.rlp_hash(block_header) - if rlp.rlp_hash(ommers) != block_header.ommers_hash: + block_hash = keccak256(rlp.encode(block_header)) + if keccak256(rlp.encode(ommers)) != block_header.ommers_hash: raise InvalidBlock if len(ommers) == 0: @@ -541,18 +541,19 @@ def validate_ommers( if len(ommers) > 2: raise InvalidBlock - ommers_hashes = [rlp.rlp_hash(ommer) for ommer in ommers] + ommers_hashes = [keccak256(rlp.encode(ommer)) for ommer in ommers] if len(ommers_hashes) != len(set(ommers_hashes)): raise InvalidBlock recent_canonical_blocks = chain.blocks[-(MAX_OMMER_DEPTH + Uint(1)) :] recent_canonical_block_hashes = { - rlp.rlp_hash(block.header) for block in recent_canonical_blocks + keccak256(rlp.encode(block.header)) + for block in recent_canonical_blocks } recent_ommers_hashes: Set[Hash32] = set() for block in recent_canonical_blocks: recent_ommers_hashes = recent_ommers_hashes.union( - {rlp.rlp_hash(ommer) for ommer in block.ommers} + {keccak256(rlp.encode(ommer)) for ommer in block.ommers} ) for ommer_index, ommer in enumerate(ommers): diff --git a/src/ethereum/tangerine_whistle/fork_types.py b/src/ethereum/tangerine_whistle/fork_types.py index 05f34f691b..00817ba506 100644 --- a/src/ethereum/tangerine_whistle/fork_types.py +++ b/src/ethereum/tangerine_whistle/fork_types.py @@ -14,11 +14,11 @@ 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 .. import rlp from ..crypto.hash import Hash32, keccak256 Address = Bytes20 diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index a9caf4990f..57d697d615 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Union +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint @@ -14,7 +15,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidSignatureError -from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 diff --git a/src/ethereum/tangerine_whistle/trie.py b/src/ethereum/tangerine_whistle/trie.py index 884faff508..f9d3d6ed97 100644 --- a/src/ethereum/tangerine_whistle/trie.py +++ b/src/ethereum/tangerine_whistle/trie.py @@ -28,6 +28,7 @@ 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 @@ -36,7 +37,6 @@ from ethereum.dao_fork import trie as previous_trie from ethereum.utils.hexadecimal import hex_to_bytes -from .. import rlp from .blocks import Receipt from .fork_types import Account, Address, Root, encode_account from .transactions import Transaction diff --git a/src/ethereum/tangerine_whistle/utils/address.py b/src/ethereum/tangerine_whistle/utils/address.py index 1bbcb3ea70..8853e91c9b 100644 --- a/src/ethereum/tangerine_whistle/utils/address.py +++ b/src/ethereum/tangerine_whistle/utils/address.py @@ -14,12 +14,12 @@ """ from typing import Union +from ethereum_rlp import rlp from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import keccak256 from ethereum.utils.byte import left_pad_zero_bytes -from ... import rlp from ..fork_types import Address diff --git a/src/ethereum_spec_tools/evm_tools/b11r/__init__.py b/src/ethereum_spec_tools/evm_tools/b11r/__init__.py index f86c533524..e106af5cd7 100644 --- a/src/ethereum_spec_tools/evm_tools/b11r/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/b11r/__init__.py @@ -6,9 +6,10 @@ import json from typing import Optional, TextIO +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes32 -from ethereum import rlp +from ethereum.crypto.hash import keccak256 from ..utils import get_stream_logger from .b11r_types import Body, Header @@ -111,7 +112,7 @@ def build_block(self) -> None: block.append(self.body.withdrawals) self.block_rlp = rlp.encode(block) - self.block_hash = rlp.rlp_hash(header_to_list) + self.block_hash = keccak256(rlp.encode(header_to_list)) def run(self) -> int: """ diff --git a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py b/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py index 3c34883012..b8b99b4fb7 100644 --- a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py +++ b/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py @@ -4,10 +4,10 @@ import json from typing import Any, List, Optional, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes20, Bytes32, Bytes256 from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp from ethereum.crypto.hash import Hash32, keccak256 from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_bytes8 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 8aa936c7ab..db6c5ade55 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -7,10 +7,10 @@ from abc import ABC, abstractmethod from typing import Any, Tuple +from ethereum_rlp import rlp from ethereum_types.numeric import U256 -from ethereum import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes8, @@ -99,7 +99,7 @@ def json_to_block( # Always decode from rlp block_rlp = hex_to_bytes(json_block["rlp"]) block = rlp.decode_to(self.fork.Block, block_rlp) - block_header_hash = rlp.rlp_hash(block.header) + block_header_hash = keccak256(rlp.encode(block.header)) return block, block_header_hash, block_rlp header = self.json_to_header(json_block["blockHeader"]) 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 e8cb7baade..76b0c25b8c 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py @@ -6,10 +6,10 @@ from dataclasses import fields from typing import Any, List +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes32, diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 0f4ed931d2..a5c2be531f 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -8,9 +8,10 @@ from functools import partial from typing import Any, TextIO +from ethereum_rlp import rlp from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp, trace +from ethereum import trace from ethereum.crypto.hash import keccak256 from ethereum.exceptions import EthereumException, InvalidBlock from ethereum_spec_tools.forks import Hardfork diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index a52dd43c0f..802c524304 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -5,10 +5,10 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp from ethereum.crypto.hash import Hash32, keccak256 from ethereum.utils.byte import left_pad_zero_bytes from ethereum.utils.hexadecimal import hex_to_bytes 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 6a0aa1ab8b..404f96bfe9 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -5,10 +5,10 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Tuple +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_u256, hex_to_uint diff --git a/src/ethereum_spec_tools/sync.py b/src/ethereum_spec_tools/sync.py index 7aba21cf73..fb4ad4df9c 100644 --- a/src/ethereum_spec_tools/sync.py +++ b/src/ethereum_spec_tools/sync.py @@ -17,10 +17,11 @@ from typing import Any, Dict, List, Optional, TypeVar, Union, cast from urllib import request +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes0, Bytes256 from ethereum_types.numeric import U64, U256, Uint -from ethereum import genesis, rlp +from ethereum import genesis from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes8, diff --git a/tests/berlin/test_ethash.py b/tests/berlin/test_ethash.py index 503de96b4f..00f3cbfa57 100644 --- a/tests/berlin/test_ethash.py +++ b/tests/berlin/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.berlin.blocks import Header from ethereum.berlin.fork import ( generate_header_hash_for_pow, diff --git a/tests/berlin/test_rlp.py b/tests/berlin/test_rlp.py index dbc5a8873b..e853f4e577 100644 --- a/tests/berlin/test_rlp.py +++ b/tests/berlin/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U64, U256, Uint -import ethereum.rlp as rlp from ethereum.berlin.blocks import Block, Header, Log, Receipt from ethereum.berlin.transactions import ( AccessListTransaction, diff --git a/tests/berlin/test_state_transition.py b/tests/berlin/test_state_transition.py index 2e71ed191c..4b09c8e07f 100644 --- a/tests/berlin/test_state_transition.py +++ b/tests/berlin/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -124,7 +124,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/berlin/test_transaction.py b/tests/berlin/test_transaction.py index ab49e80f0f..9c86d1643b 100644 --- a/tests/berlin/test_transaction.py +++ b/tests/berlin/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.berlin.fork import calculate_intrinsic_cost, validate_transaction from ethereum.berlin.transactions import LegacyTransaction from ethereum.utils.hexadecimal import hex_to_uint diff --git a/tests/byzantium/test_ethash.py b/tests/byzantium/test_ethash.py index 31f5e39641..409ed6b986 100644 --- a/tests/byzantium/test_ethash.py +++ b/tests/byzantium/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.byzantium.blocks import Header from ethereum.byzantium.fork import ( generate_header_hash_for_pow, diff --git a/tests/byzantium/test_rlp.py b/tests/byzantium/test_rlp.py index 0a1cae29c4..fa0985ac9c 100644 --- a/tests/byzantium/test_rlp.py +++ b/tests/byzantium/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.byzantium.blocks import Block, Header, Log, Receipt from ethereum.byzantium.transactions import Transaction from ethereum.byzantium.utils.hexadecimal import hex_to_address diff --git a/tests/byzantium/test_state_transition.py b/tests/byzantium/test_state_transition.py index 6d9e7317c8..c392873f78 100644 --- a/tests/byzantium/test_state_transition.py +++ b/tests/byzantium/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -136,7 +136,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/byzantium/test_transaction.py b/tests/byzantium/test_transaction.py index 8d8f8b5ea6..a133df527d 100644 --- a/tests/byzantium/test_transaction.py +++ b/tests/byzantium/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.byzantium.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/cancun/test_rlp.py b/tests/cancun/test_rlp.py index e070ca566a..6478fe25d9 100644 --- a/tests/cancun/test_rlp.py +++ b/tests/cancun/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 from ethereum_types.numeric import U64, U256, Uint -import ethereum.rlp as rlp from ethereum.cancun.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.cancun.transactions import ( AccessListTransaction, diff --git a/tests/constantinople/test_ethash.py b/tests/constantinople/test_ethash.py index 7712029398..f608de94f9 100644 --- a/tests/constantinople/test_ethash.py +++ b/tests/constantinople/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.constantinople.blocks import Header from ethereum.constantinople.fork import ( generate_header_hash_for_pow, diff --git a/tests/constantinople/test_rlp.py b/tests/constantinople/test_rlp.py index 4a6b5d2df9..bdb13cc502 100644 --- a/tests/constantinople/test_rlp.py +++ b/tests/constantinople/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.constantinople.blocks import Block, Header, Log, Receipt from ethereum.constantinople.transactions import Transaction from ethereum.constantinople.utils.hexadecimal import hex_to_address diff --git a/tests/constantinople/test_state_transition.py b/tests/constantinople/test_state_transition.py index 67b29460ac..1eda69979c 100644 --- a/tests/constantinople/test_state_transition.py +++ b/tests/constantinople/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -139,7 +139,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/constantinople/test_transaction.py b/tests/constantinople/test_transaction.py index 1f03190047..0916fedcbd 100644 --- a/tests/constantinople/test_transaction.py +++ b/tests/constantinople/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.constantinople.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/frontier/test_ethash.py b/tests/frontier/test_ethash.py index 124708982c..64fc675866 100644 --- a/tests/frontier/test_ethash.py +++ b/tests/frontier/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.ethash import ( cache_size, diff --git a/tests/frontier/test_rlp.py b/tests/frontier/test_rlp.py index f25223e9ef..c39b6ae15e 100644 --- a/tests/frontier/test_rlp.py +++ b/tests/frontier/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.frontier.blocks import Block, Header, Log, Receipt from ethereum.frontier.transactions import Transaction diff --git a/tests/frontier/test_state_transition.py b/tests/frontier/test_state_transition.py index 048c90166e..840fe3a9ca 100644 --- a/tests/frontier/test_state_transition.py +++ b/tests/frontier/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -130,7 +130,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/frontier/test_transaction.py b/tests/frontier/test_transaction.py index 59f524565b..6b0c12f2e8 100644 --- a/tests/frontier/test_transaction.py +++ b/tests/frontier/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.frontier.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/helpers/load_state_tests.py b/tests/helpers/load_state_tests.py index e8ffe5b1b2..eca1953ae3 100644 --- a/tests/helpers/load_state_tests.py +++ b/tests/helpers/load_state_tests.py @@ -8,9 +8,11 @@ import pytest from _pytest.mark.structures import ParameterSet +from ethereum_rlp import rlp +from ethereum_rlp.exceptions import RLPException from ethereum_types.numeric import U64 -from ethereum import rlp +from ethereum.crypto.hash import keccak256 from ethereum.exceptions import EthereumException from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load @@ -47,7 +49,7 @@ def run_blockchain_st_test(test_case: Dict, load: Load) -> None: genesis_block = load.fork.Block(*parameters) genesis_header_hash = hex_to_bytes(json_data["genesisBlockHeader"]["hash"]) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_rlp = hex_to_bytes(json_data["genesisRLP"]) assert rlp.encode(genesis_block) == genesis_rlp @@ -72,14 +74,14 @@ def run_blockchain_st_test(test_case: Dict, load: Load) -> None: # TODO: Once all the specific exception types are thrown, # only `pytest.raises` the correct exception type instead of # all of them. - with pytest.raises(EthereumException): + with pytest.raises((EthereumException, RLPException)): add_block_to_chain(chain, json_block, load, mock_pow) return else: add_block_to_chain(chain, json_block, load, mock_pow) last_block_hash = hex_to_bytes(json_data["lastblockhash"]) - assert rlp.rlp_hash(chain.blocks[-1].header) == last_block_hash + assert keccak256(rlp.encode(chain.blocks[-1].header)) == last_block_hash expected_post_state = load.json_to_state(json_data["postState"]) assert chain.state == expected_post_state @@ -96,7 +98,7 @@ def add_block_to_chain( block_rlp, ) = load.json_to_block(json_block) - assert rlp.rlp_hash(block.header) == block_header_hash + assert keccak256(rlp.encode(block.header)) == block_header_hash assert rlp.encode(block) == block_rlp if not mock_pow: diff --git a/tests/helpers/load_vm_tests.py b/tests/helpers/load_vm_tests.py index ecfd081fe1..6469f58e3d 100644 --- a/tests/helpers/load_vm_tests.py +++ b/tests/helpers/load_vm_tests.py @@ -3,9 +3,9 @@ from importlib import import_module from typing import Any, List +from ethereum_rlp import rlp from ethereum_types.numeric import U64, U256, Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.utils.hexadecimal import ( hex_to_bytes, diff --git a/tests/homestead/test_ethash.py b/tests/homestead/test_ethash.py index e4b9164e2f..0d651d7bb5 100644 --- a/tests/homestead/test_ethash.py +++ b/tests/homestead/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.ethash import ( cache_size, diff --git a/tests/homestead/test_rlp.py b/tests/homestead/test_rlp.py index 1f02865bde..647ddcafc5 100644 --- a/tests/homestead/test_rlp.py +++ b/tests/homestead/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.homestead.blocks import Block, Header, Log, Receipt from ethereum.homestead.transactions import Transaction diff --git a/tests/homestead/test_state_transition.py b/tests/homestead/test_state_transition.py index 51bee07ff1..978ee95512 100644 --- a/tests/homestead/test_state_transition.py +++ b/tests/homestead/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -212,7 +212,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/homestead/test_transaction.py b/tests/homestead/test_transaction.py index 03df4018cf..5fff1c1bbc 100644 --- a/tests/homestead/test_transaction.py +++ b/tests/homestead/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.homestead.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/istanbul/test_ethash.py b/tests/istanbul/test_ethash.py index de1b421240..ea72cdc935 100644 --- a/tests/istanbul/test_ethash.py +++ b/tests/istanbul/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.ethash import ( cache_size, diff --git a/tests/istanbul/test_rlp.py b/tests/istanbul/test_rlp.py index 84c21b03e7..3eb39041ac 100644 --- a/tests/istanbul/test_rlp.py +++ b/tests/istanbul/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.istanbul.blocks import Block, Header, Log, Receipt from ethereum.istanbul.transactions import Transaction diff --git a/tests/istanbul/test_state_transition.py b/tests/istanbul/test_state_transition.py index c57132121d..1bb00f785b 100644 --- a/tests/istanbul/test_state_transition.py +++ b/tests/istanbul/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -126,7 +126,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/istanbul/test_transaction.py b/tests/istanbul/test_transaction.py index c2bf2e2e6d..6d0719fc3c 100644 --- a/tests/istanbul/test_transaction.py +++ b/tests/istanbul/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.istanbul.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/london/test_rlp.py b/tests/london/test_rlp.py index 19ed3d4dd4..a18856e6e7 100644 --- a/tests/london/test_rlp.py +++ b/tests/london/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U64, U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.london.blocks import Block, Header, Log, Receipt from ethereum.london.transactions import ( diff --git a/tests/london/test_state_transition.py b/tests/london/test_state_transition.py index 00c8f3f96a..15e7460095 100644 --- a/tests/london/test_state_transition.py +++ b/tests/london/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -127,7 +127,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "4a62c29ca7f3a61e5519eabbf57a40bb28ee1f164839b3160281c30d2443a69e" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/london/test_transaction.py b/tests/london/test_transaction.py index c519d7aa83..21b418d62c 100644 --- a/tests/london/test_transaction.py +++ b/tests/london/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.london.fork import calculate_intrinsic_cost, validate_transaction from ethereum.london.transactions import LegacyTransaction from ethereum.utils.hexadecimal import hex_to_uint diff --git a/tests/paris/test_rlp.py b/tests/paris/test_rlp.py index aa9c582f32..33497fde81 100644 --- a/tests/paris/test_rlp.py +++ b/tests/paris/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 from ethereum_types.numeric import U64, U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.paris.blocks import Block, Header, Log, Receipt from ethereum.paris.transactions import ( diff --git a/tests/paris/test_state_transition.py b/tests/paris/test_state_transition.py index 8ab6cc958f..5ac08aaa22 100644 --- a/tests/paris/test_state_transition.py +++ b/tests/paris/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -127,7 +127,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "4a62c29ca7f3a61e5519eabbf57a40bb28ee1f164839b3160281c30d2443a69e" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/shanghai/test_rlp.py b/tests/shanghai/test_rlp.py index 34c19612a9..7a7d689ef6 100644 --- a/tests/shanghai/test_rlp.py +++ b/tests/shanghai/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8, Bytes32 from ethereum_types.numeric import U64, U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.shanghai.blocks import Block, Header, Log, Receipt, Withdrawal from ethereum.shanghai.transactions import ( diff --git a/tests/spurious_dragon/test_ethash.py b/tests/spurious_dragon/test_ethash.py index 3605bc2c94..7a50cf3fe9 100644 --- a/tests/spurious_dragon/test_ethash.py +++ b/tests/spurious_dragon/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.ethash import ( cache_size, diff --git a/tests/spurious_dragon/test_rlp.py b/tests/spurious_dragon/test_rlp.py index 63d1bd4696..05506ef6f1 100644 --- a/tests/spurious_dragon/test_rlp.py +++ b/tests/spurious_dragon/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.spurious_dragon.blocks import Block, Header, Log, Receipt from ethereum.spurious_dragon.transactions import Transaction diff --git a/tests/spurious_dragon/test_state_transition.py b/tests/spurious_dragon/test_state_transition.py index 278e6970c0..d3af5e4788 100644 --- a/tests/spurious_dragon/test_state_transition.py +++ b/tests/spurious_dragon/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -130,7 +130,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/spurious_dragon/test_transaction.py b/tests/spurious_dragon/test_transaction.py index 2a53546074..fc70c08d14 100644 --- a/tests/spurious_dragon/test_transaction.py +++ b/tests/spurious_dragon/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.spurious_dragon.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/tangerine_whistle/test_ethash.py b/tests/tangerine_whistle/test_ethash.py index a74ea86fea..fe82d033d8 100644 --- a/tests/tangerine_whistle/test_ethash.py +++ b/tests/tangerine_whistle/test_ethash.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, cast import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import Uint -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.ethash import ( cache_size, diff --git a/tests/tangerine_whistle/test_rlp.py b/tests/tangerine_whistle/test_rlp.py index cee5ddaba7..3a4a30074a 100644 --- a/tests/tangerine_whistle/test_rlp.py +++ b/tests/tangerine_whistle/test_rlp.py @@ -1,8 +1,8 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes0, Bytes8 from ethereum_types.numeric import U256, Uint -import ethereum.rlp as rlp from ethereum.crypto.hash import keccak256 from ethereum.tangerine_whistle.blocks import Block, Header, Log, Receipt from ethereum.tangerine_whistle.transactions import Transaction diff --git a/tests/tangerine_whistle/test_state_transition.py b/tests/tangerine_whistle/test_state_transition.py index 55768954d9..c56202145b 100644 --- a/tests/tangerine_whistle/test_state_transition.py +++ b/tests/tangerine_whistle/test_state_transition.py @@ -2,11 +2,11 @@ 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 import rlp -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock from tests.helpers import TEST_FIXTURES from tests.helpers.load_state_tests import ( @@ -132,7 +132,7 @@ def test_transaction_with_insufficient_balance_for_value() -> None: "0b22b0d49035cb4f8a969d584f36126e0ac6996b9db7264ac5a192b8698177eb" ) - assert rlp.rlp_hash(genesis_header) == genesis_header_hash + assert keccak256(rlp.encode(genesis_header)) == genesis_header_hash genesis_block = FIXTURES_LOADER.fork.Block( genesis_header, diff --git a/tests/tangerine_whistle/test_transaction.py b/tests/tangerine_whistle/test_transaction.py index b99a80f469..7e3a253fdb 100644 --- a/tests/tangerine_whistle/test_transaction.py +++ b/tests/tangerine_whistle/test_transaction.py @@ -1,8 +1,8 @@ from functools import partial import pytest +from ethereum_rlp import rlp -from ethereum import rlp from ethereum.tangerine_whistle.fork import ( calculate_intrinsic_cost, validate_transaction, diff --git a/tests/test_genesis.py b/tests/test_genesis.py index a75d73adfe..3dc725c151 100644 --- a/tests/test_genesis.py +++ b/tests/test_genesis.py @@ -1,7 +1,7 @@ import pytest +from ethereum_rlp import rlp from ethereum_types.numeric import U64 -from ethereum import rlp from ethereum.crypto.hash import keccak256 from ethereum.frontier.blocks import Block, Header from ethereum.frontier.fork import BlockChain diff --git a/tests/test_rlp.py b/tests/test_rlp.py index 2c1a0f2d19..1a845d43a2 100644 --- a/tests/test_rlp.py +++ b/tests/test_rlp.py @@ -1,346 +1,17 @@ import json import os -from typing import List, Sequence, Tuple, Union, cast +from typing import List, Sequence, Tuple, Union import pytest +from ethereum_rlp import Extended, rlp -from ethereum import rlp -from ethereum.exceptions import RLPDecodingError, RLPEncodingError -from ethereum.frontier.fork_types import U256, Bytes, Uint -from ethereum.rlp import Extended +from ethereum.frontier.fork_types import Bytes, Uint from ethereum.utils.hexadecimal import hex_to_bytes from tests.helpers import TEST_FIXTURES ETHEREUM_TESTS_PATH = TEST_FIXTURES["ethereum_tests"]["fixture_path"] -# -# Tests for RLP encode -# - -# -# Testing bytes encoding -# - - -def test_rlp_encode_empty_bytes() -> None: - assert rlp.encode_bytes(b"") == bytearray([0x80]) - assert rlp.encode_bytes(bytearray()) == bytearray([0x80]) - - -def test_rlp_encode_single_byte_val_less_than_128() -> None: - assert rlp.encode_bytes(b"x") == bytearray([0x78]) - assert rlp.encode_bytes(bytearray(b"x")) == bytearray([0x78]) - - -def test_rlp_encode_single_byte_val_equal_128() -> None: - assert rlp.encode_bytes(b"\x80") == b"\x81\x80" - assert rlp.encode_bytes(bytearray(b"\x80")) == b"\x81\x80" - - -def test_rlp_encode_single_byte_val_greater_than_128() -> None: - assert rlp.encode_bytes(b"\x83") == bytearray([0x81, 0x83]) - assert rlp.encode_bytes(bytearray(b"\x83")) == bytearray([0x81, 0x83]) - - -def test_rlp_encode_55_bytes() -> None: - assert rlp.encode_bytes(b"\x83" * 55) == bytearray([0xB7]) + bytearray( - b"\x83" * 55 - ) - assert rlp.encode_bytes(bytearray(b"\x83") * 55) == bytearray( - [0xB7] - ) + bytearray(b"\x83" * 55) - - -def test_rlp_encode_large_bytes() -> None: - assert rlp.encode_bytes(b"\x83" * 2**20) == ( - bytearray([0xBA]) - + bytearray(b"\x10\x00\x00") - + bytearray(b"\x83" * 2**20) - ) - assert rlp.encode_bytes(bytearray(b"\x83") * 2**20) == ( - bytearray([0xBA]) - + bytearray(b"\x10\x00\x00") - + bytearray(b"\x83" * 2**20) - ) - - -# -# Testing uint and u256 encoding -# - - -def test_rlp_encode_uint_0() -> None: - assert rlp.encode(Uint(0)) == b"\x80" - - -def test_rlp_encode_uint_byte_max() -> None: - assert rlp.encode(Uint(255)) == b"\x81\xff" - - -def test_rlp_encode_uint256_0() -> None: - assert rlp.encode(U256(0)) == b"\x80" - - -def test_rlp_encode_uint256_byte_max() -> None: - assert rlp.encode(U256(255)) == b"\x81\xff" - - -# -# Testing str encoding -# - - -def test_rlp_encode_empty_str() -> None: - assert rlp.encode("") == b"\x80" - - -def test_rlp_encode_one_char_str() -> None: - assert rlp.encode("h") == b"h" - - -def test_rlp_encode_multi_char_str() -> None: - assert rlp.encode("hello") == b"\x85hello" - - -# -# Testing sequence encoding -# - - -def test_rlp_encode_empty_sequence() -> None: - assert rlp.encode_sequence([]) == bytearray([0xC0]) - - -def test_rlp_encode_single_elem_list_byte() -> None: - assert rlp.encode_sequence([b"hello"]) == bytearray([0xC6]) + b"\x85hello" - - -def test_rlp_encode_single_elem_list_uint() -> None: - assert rlp.encode_sequence([Uint(255)]) == bytearray([0xC2]) + b"\x81\xff" - - -def test_rlp_encode_10_elem_byte_uint_combo() -> None: - raw_data = [b"hello"] * 5 + [Uint(35)] * 5 - expected = ( - bytearray([0xE3]) - + b"\x85hello\x85hello\x85hello\x85hello\x85hello#####" - ) - assert rlp.encode_sequence(raw_data) == expected - - -def test_rlp_encode_20_elem_byte_uint_combo() -> None: - raw_data = [Uint(35)] * 10 + [b"hello"] * 10 - expected = ( - bytearray([0xF8]) - + b"F" - + b"##########\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello" - ) - assert rlp.encode_sequence(raw_data) == expected - - -def test_rlp_encode_nested_sequence() -> None: - nested_sequence: Sequence["Extended"] = [ - b"hello", - Uint(255), - [b"how", [b"are", b"you", [b"doing"]]], - ] - expected: Bytes = ( - b"\xdd\x85hello\x81\xff\xd4\x83how\xcf\x83are\x83you\xc6\x85doing" - ) - assert rlp.encode_sequence(nested_sequence) == expected - - -def test_rlp_encode_successfully() -> None: - test_cases: List[Tuple[rlp.Extended, Union[bytes, bytearray]]] = [ - (b"", bytearray([0x80])), - (b"\x83" * 55, bytearray([0xB7]) + bytearray(b"\x83" * 55)), - (Uint(0), b"\x80"), - (Uint(255), b"\x81\xff"), - ([], bytearray([0xC0])), - ( - [b"hello"] * 5 + [Uint(35)] * 5, - bytearray([0xE3]) - + bytearray(b"\x85hello\x85hello\x85hello\x85hello\x85hello#####"), - ), - ( - [b"hello", Uint(255), [b"how", [b"are", b"you", [b"doing"]]]], - bytearray( - b"\xdd\x85hello\x81\xff\xd4\x83how\xcf\x83are\x83you\xc6\x85doing" - ), - ), - ] - for raw_data, expected_encoding in test_cases: - assert rlp.encode(raw_data) == expected_encoding - - -def test_rlp_encode_fails() -> None: - test_cases = [ - 123, - [b"hello", Uint(255), [b"how", [b"are", [b"you", [123]]]]], - ] - for raw_data in test_cases: - with pytest.raises(RLPEncodingError): - rlp.encode(cast(Extended, raw_data)) - - -# -# Tests for RLP decode -# - -# -# Testing bytes decoding -# - - -def test_rlp_decode_to_empty_bytes() -> None: - assert rlp.decode_to_bytes(bytearray([0x80])) == b"" - - -def test_rlp_decode_to_single_byte_less_than_128() -> None: - assert rlp.decode_to_bytes(bytearray([0])) == bytearray([0]) - assert rlp.decode_to_bytes(bytearray([0x78])) == bytearray([0x78]) - - -def test_rlp_decode_to_single_byte_gte_128() -> None: - assert rlp.decode_to_bytes(bytearray([0x81, 0x83])) == b"\x83" - assert rlp.decode_to_bytes(b"\x81\x80") == b"\x80" - - -def test_rlp_decode_to_55_bytes() -> None: - encoding = bytearray([0xB7]) + bytearray(b"\x83" * 55) - expected_raw_data = bytearray(b"\x83") * 55 - assert rlp.decode_to_bytes(encoding) == expected_raw_data - - -def test_rlp_decode_to_large_bytes() -> None: - encoding = bytearray([0xBA]) + b"\x10\x00\x00" + b"\x83" * (2**20) - expected_raw_data = b"\x83" * (2**20) - assert rlp.decode_to_bytes(encoding) == expected_raw_data - - -# -# Testing uint decoding -# - - -def test_rlp_decode_to_zero_uint() -> None: - assert rlp.decode(b"\x80") == Uint(0).to_be_bytes() - - -def test_rlp_decode_to_255_uint() -> None: - assert rlp.decode(b"\x81\xff") == Uint(255).to_be_bytes() - - -# -# Testing string decoding -# - - -def test_rlp_decode_empty_str() -> None: - assert rlp.decode(b"\x80") == "".encode() - - -def test_rlp_decode_one_char_str() -> None: - assert rlp.decode(b"h") == "h".encode() - - -def test_rlp_decode_multi_char_str() -> None: - assert rlp.decode(b"\x85hello") == "hello".encode() - - -# -# Testing sequence decoding -# - - -def test_rlp_decode_to_empty_sequence() -> None: - assert rlp.decode_to_sequence(bytearray([0xC0])) == [] - - -def test_rlp_decode_to_1_elem_sequence_of_byte() -> None: - assert rlp.decode_to_sequence(bytearray([0xC6]) + b"\x85hello") == [ - b"hello" - ] - - -def test_rlp_decode_to_1_elem_sequence_of_uint() -> None: - assert rlp.decode_to_sequence(bytearray([0xC2]) + b"\x81\xff") == [ - Uint(255).to_be_bytes() - ] - - -def test_rlp_decode_to_10_elem_sequence_of_bytes_and_uints() -> None: - encoded_data = ( - bytearray([0xE3]) - + b"\x85hello\x85hello\x85hello\x85hello\x85hello#####" - ) - expected_raw_data = [b"hello"] * 5 + [Uint(35).to_be_bytes()] * 5 - assert rlp.decode_to_sequence(encoded_data) == expected_raw_data - - -def test_rlp_decode_to_20_elem_sequence_of_bytes_and_uints() -> None: - encoded_data = ( - bytearray([0xF8]) - + b"F" - + b"\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello\x85hello##########" - ) - expected_raw_data = [b"hello"] * 10 + [Uint(35).to_be_bytes()] * 10 - assert rlp.decode_to_sequence(encoded_data) == expected_raw_data - - -def test_rlp_decode_to_nested_sequence() -> None: - encoded_data = ( - b"\xdf\x85hello\x81\xff\xd6\x83how\xd1\x83are\x83you\xc8\x85doing\xc1#" - ) - expected_raw_data = [ - b"hello", - Uint(255).to_be_bytes(), - [ - b"how", - [b"are", b"you", [b"doing", [Uint(35).to_be_bytes()]]], - ], - ] - assert rlp.decode_to_sequence(encoded_data) == expected_raw_data - - -def test_rlp_decode_successfully() -> None: - test_cases = [ - (bytearray([0x80]), bytearray()), - (bytearray([0xB7]) + bytearray(b"\x83" * 55), bytearray(b"\x83") * 55), - (bytearray([0xC0]), []), - ( - b"\xdb\x85hello\xd4\x83how\xcf\x83are\x83you\xc6\x85doing", - [b"hello", [b"how", [b"are", b"you", [b"doing"]]]], - ), - ] - for encoding, expected_raw_data in test_cases: - assert rlp.decode(encoding) == expected_raw_data - - -def test_rlp_decode_failure_empty_bytes() -> None: - with pytest.raises(RLPDecodingError): - rlp.decode(b"") - - -def test_roundtrip_encoding_and_decoding() -> None: - test_cases: List[Extended] = [ - b"", - b"h", - b"hello how are you doing today?", - Uint(35).to_be_bytes(), - Uint(255).to_be_bytes(), - [], - [ - b"hello", - [b"how", [b"are", b"you", [b"doing", [Uint(255).to_be_bytes()]]]], - ], - [[b"hello", b"world"], [b"how", b"are"], [b"you", b"doing"]], - ] - for raw_data in test_cases: - assert rlp.decode(rlp.encode(raw_data)) == raw_data - - # # Running ethereum/tests for rlp # @@ -413,5 +84,5 @@ def test_ethtest_fixtures_for_successfully_rlp_decoding( def test_ethtest_fixtures_for_fails_in_rlp_decoding( raw_data: Bytes, encoded_data: Bytes ) -> None: - with pytest.raises(RLPDecodingError): + with pytest.raises(rlp.DecodingError): rlp.decode(encoded_data) From 697de95c1cc2d92a2c9f465eb34e79dcd06f8cca Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Sat, 16 Nov 2024 21:24:31 -0500 Subject: [PATCH 3/5] Force forks to deal with blocks from previous forks --- src/ethereum/arrow_glacier/blocks.py | 29 +++++++++++++- src/ethereum/arrow_glacier/fork.py | 40 ++++++++++++++----- src/ethereum/berlin/blocks.py | 17 ++++++++ src/ethereum/berlin/fork.py | 13 ++++-- src/ethereum/byzantium/blocks.py | 19 ++++++++- src/ethereum/byzantium/fork.py | 13 ++++-- src/ethereum/cancun/blocks.py | 29 +++++++++++++- src/ethereum/cancun/fork.py | 39 +++++++++++++----- src/ethereum/cancun/vm/gas.py | 4 +- src/ethereum/constantinople/blocks.py | 19 ++++++++- src/ethereum/constantinople/fork.py | 13 ++++-- src/ethereum/dao_fork/blocks.py | 19 ++++++++- src/ethereum/dao_fork/fork.py | 13 ++++-- src/ethereum/frontier/blocks.py | 15 +++++++ src/ethereum/frontier/fork.py | 20 +++++----- src/ethereum/gray_glacier/blocks.py | 29 +++++++++++++- src/ethereum/gray_glacier/fork.py | 40 ++++++++++++++----- src/ethereum/homestead/blocks.py | 19 ++++++++- src/ethereum/homestead/fork.py | 13 ++++-- src/ethereum/istanbul/blocks.py | 19 ++++++++- src/ethereum/istanbul/fork.py | 13 ++++-- src/ethereum/london/blocks.py | 29 +++++++++++++- src/ethereum/london/fork.py | 28 +++++++++---- src/ethereum/muir_glacier/blocks.py | 19 ++++++++- src/ethereum/muir_glacier/fork.py | 13 ++++-- src/ethereum/paris/blocks.py | 29 +++++++++++++- src/ethereum/paris/fork.py | 38 +++++++++++++----- src/ethereum/shanghai/blocks.py | 29 +++++++++++++- src/ethereum/shanghai/fork.py | 39 +++++++++++++----- src/ethereum/spurious_dragon/blocks.py | 19 ++++++++- src/ethereum/spurious_dragon/fork.py | 13 ++++-- src/ethereum/tangerine_whistle/blocks.py | 19 ++++++++- src/ethereum/tangerine_whistle/fork.py | 13 ++++-- .../lint/lints/glacier_forks_hygiene.py | 8 ++-- 34 files changed, 610 insertions(+), 121 deletions(-) diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index 87c9acac7f..bbc37b1e9d 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, Tuple, Union from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias + +from ethereum.london import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -45,6 +48,13 @@ class Header: base_fee_per_gas: Uint +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -57,6 +67,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -80,3 +97,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return previous_blocks.header_base_fee_per_gas(header) diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index 2108a5fba7..2eb045138a 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -22,9 +22,18 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.london import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -58,6 +67,7 @@ GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) GAS_LIMIT_MINIMUM = Uint(5000) MINIMUM_DIFFICULTY = Uint(131072) +INITIAL_BASE_FEE = Uint(1000000000) MAX_OMMER_DEPTH = Uint(6) BOMB_DELAY_BLOCKS = 10700000 EMPTY_OMMER_HASH = keccak256(rlp.encode([])) @@ -69,7 +79,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -256,7 +266,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -274,15 +284,25 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + 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, - ) + expected_base_fee_per_gas = INITIAL_BASE_FEE + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: + # For every block except the first, calculate the base fee per gas + # based on the parent block. + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: raise InvalidBlock @@ -627,7 +647,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index df486c7a9d..5d36c734c8 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -14,6 +14,9 @@ from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias + +from ethereum.muir_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 8ec009f424..845c77214e 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -22,9 +22,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.muir_glacier import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -66,7 +67,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -190,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -208,6 +209,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -521,7 +526,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/byzantium/blocks.py b/src/ethereum/byzantium/blocks.py index f74ead6616..7a01dfde6f 100644 --- a/src/ethereum/byzantium/blocks.py +++ b/src/ethereum/byzantium/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.spurious_dragon import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index 68001e3ee1..d18ccfbe28 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -22,9 +22,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.spurious_dragon import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -62,7 +63,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -186,7 +187,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -204,6 +205,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -511,7 +516,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index 54ef88e15f..ff7d8c5ceb 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, 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 typing_extensions import TypeAlias + +from ethereum.shanghai import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -62,6 +65,13 @@ class Header: parent_beacon_block_root: Root +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -75,6 +85,13 @@ class Block: withdrawals: Tuple[Withdrawal, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -98,3 +115,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return previous_blocks.header_base_fee_per_gas(header) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 531de2ed5a..315529cbff 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -21,9 +21,19 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.shanghai import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + Withdrawal, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root, VersionedHash from .state import ( @@ -65,6 +75,7 @@ ELASTICITY_MULTIPLIER = Uint(2) GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) GAS_LIMIT_MINIMUM = Uint(5000) +INITIAL_BASE_FEE = Uint(1000000000) EMPTY_OMMER_HASH = keccak256(rlp.encode([])) SYSTEM_ADDRESS = hex_to_address("0xfffffffffffffffffffffffffffffffffffffffe") BEACON_ROOTS_ADDRESS = hex_to_address( @@ -81,7 +92,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -279,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -297,15 +308,25 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + 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, - ) + expected_base_fee_per_gas = INITIAL_BASE_FEE + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: + # For every block except the first, calculate the base fee per gas + # based on the parent block. + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: raise InvalidBlock if header.timestamp <= parent_header.timestamp: diff --git a/src/ethereum/cancun/vm/gas.py b/src/ethereum/cancun/vm/gas.py index cc3ccc7c6b..76274edbac 100644 --- a/src/ethereum/cancun/vm/gas.py +++ b/src/ethereum/cancun/vm/gas.py @@ -19,7 +19,7 @@ from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential -from ..blocks import Header +from ..blocks import AnyHeader, Header from ..transactions import BlobTransaction, Transaction from . import Evm from .exceptions import OutOfGasError @@ -269,7 +269,7 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas(parent_header: AnyHeader) -> U64: """ Calculated the excess blob gas for the current block based on the gas used in the parent block. diff --git a/src/ethereum/constantinople/blocks.py b/src/ethereum/constantinople/blocks.py index f74ead6616..ca2b2b8673 100644 --- a/src/ethereum/constantinople/blocks.py +++ b/src/ethereum/constantinople/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.byzantium import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index e195589a13..f9d025300c 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -19,12 +19,13 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint +from ethereum.byzantium import fork as previous_fork from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -62,7 +63,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -186,7 +187,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -204,6 +205,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -511,7 +516,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/dao_fork/blocks.py b/src/ethereum/dao_fork/blocks.py index 713b5aecf5..541d2b757c 100644 --- a/src/ethereum/dao_fork/blocks.py +++ b/src/ethereum/dao_fork/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.homestead import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 1835a2525f..bf131ff3bd 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -24,9 +24,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.homestead import fork as previous_fork from . import FORK_CRITERIA, vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .dao import apply_dao from .fork_types import Address, Bloom, Root @@ -62,7 +63,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -193,7 +194,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -211,6 +212,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -517,7 +522,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/frontier/blocks.py b/src/ethereum/frontier/blocks.py index 713b5aecf5..e05d8ffbbe 100644 --- a/src/ethereum/frontier/blocks.py +++ b/src/ethereum/frontier/blocks.py @@ -14,6 +14,7 @@ from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +45,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Header +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +64,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Block +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 46749732f4..f6537d9dae 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -24,7 +24,7 @@ from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -59,7 +59,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -126,7 +126,7 @@ def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]: return recent_block_hashes -def state_transition(chain: BlockChain, block: Block) -> None: +def state_transition(chain: BlockChain, block: AnyBlock) -> None: """ Attempts to apply a block to an existing block chain. @@ -182,7 +182,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -225,7 +225,7 @@ def validate_header(header: Header, parent_header: Header) -> None: validate_proof_of_work(header) -def generate_header_hash_for_pow(header: Header) -> Hash32: +def generate_header_hash_for_pow(header: AnyHeader) -> Hash32: """ Generate rlp hash of the header which is to be used for Proof-of-Work verification. @@ -267,7 +267,7 @@ def generate_header_hash_for_pow(header: Header) -> Hash32: return keccak256(rlp.encode(header_data_without_pow_artefacts)) -def validate_proof_of_work(header: Header) -> None: +def validate_proof_of_work(header: AnyHeader) -> None: """ Validates the Proof of Work constraints. @@ -400,7 +400,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> ApplyBodyOutput: """ Executes a block. @@ -499,7 +499,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: AnyHeader, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. @@ -580,7 +580,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. @@ -702,7 +702,7 @@ def process_transaction( return total_gas_used, output.logs -def compute_header_hash(header: Header) -> Hash32: +def compute_header_hash(header: AnyHeader) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index 87c9acac7f..2e60d66a3f 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, Tuple, Union from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias + +from ethereum.arrow_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -45,6 +48,13 @@ class Header: base_fee_per_gas: Uint +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -57,6 +67,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -80,3 +97,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return previous_blocks.header_base_fee_per_gas(header) diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index ce070e7c08..49dfd54ba4 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -19,12 +19,21 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint +from ethereum.arrow_glacier import fork as previous_fork from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -58,6 +67,7 @@ GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) GAS_LIMIT_MINIMUM = Uint(5000) MINIMUM_DIFFICULTY = Uint(131072) +INITIAL_BASE_FEE = Uint(1000000000) MAX_OMMER_DEPTH = Uint(6) BOMB_DELAY_BLOCKS = 11400000 EMPTY_OMMER_HASH = keccak256(rlp.encode([])) @@ -69,7 +79,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -256,7 +266,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -274,15 +284,25 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + 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, - ) + expected_base_fee_per_gas = INITIAL_BASE_FEE + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: + # For every block except the first, calculate the base fee per gas + # based on the parent block. + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: raise InvalidBlock @@ -627,7 +647,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/homestead/blocks.py b/src/ethereum/homestead/blocks.py index 713b5aecf5..f7063af2a0 100644 --- a/src/ethereum/homestead/blocks.py +++ b/src/ethereum/homestead/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.frontier import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index 1419c87b6d..4977ef2596 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -22,9 +22,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.frontier import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -59,7 +60,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -182,7 +183,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -200,6 +201,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -499,7 +504,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/istanbul/blocks.py b/src/ethereum/istanbul/blocks.py index f74ead6616..84dd6b77e4 100644 --- a/src/ethereum/istanbul/blocks.py +++ b/src/ethereum/istanbul/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.constantinople import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 35eb37c9c5..90d64d8a43 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -19,12 +19,13 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint +from ethereum.constantinople import fork as previous_fork from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -62,7 +63,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -186,7 +187,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -204,6 +205,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -512,7 +517,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 87c9acac7f..461a018240 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, Tuple, Union from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias + +from ethereum.berlin import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -45,6 +48,13 @@ class Header: base_fee_per_gas: Uint +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -57,6 +67,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -80,3 +97,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return None diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index a9599345c9..b90808281b 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -19,12 +19,21 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint +from ethereum.berlin import fork as previous_fork from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError -from . import FORK_CRITERIA, vm -from .blocks import Block, Header, Log, Receipt +from . import vm +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -70,7 +79,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -257,7 +266,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -275,18 +284,23 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + if header.gas_used > header.gas_limit: raise InvalidBlock expected_base_fee_per_gas = INITIAL_BASE_FEE - if header.number != FORK_CRITERIA.block_number: + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: # For every block except the first, calculate the base fee per gas # based on the parent block. 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, + parent_base_fee_per_gas, ) if expected_base_fee_per_gas != header.base_fee_per_gas: @@ -633,7 +647,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/muir_glacier/blocks.py b/src/ethereum/muir_glacier/blocks.py index f74ead6616..76a72c924a 100644 --- a/src/ethereum/muir_glacier/blocks.py +++ b/src/ethereum/muir_glacier/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.istanbul import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index 4060049c84..d68e091c1e 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -22,9 +22,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.istanbul import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -62,7 +63,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -186,7 +187,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -204,6 +205,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH if header.timestamp <= parent_header.timestamp: raise InvalidBlock @@ -512,7 +517,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index 5bfb604816..3ac06626f0 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, Tuple, Union from ethereum_types.bytes import Bytes, Bytes8, Bytes32 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U256, Uint +from typing_extensions import TypeAlias + +from ethereum.gray_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -45,6 +48,13 @@ class Header: base_fee_per_gas: Uint +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -57,6 +67,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -80,3 +97,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return previous_blocks.header_base_fee_per_gas(header) diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index 1e34b33159..b74b067bfe 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -21,9 +21,18 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.gray_glacier import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -54,6 +63,7 @@ ELASTICITY_MULTIPLIER = Uint(2) GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) GAS_LIMIT_MINIMUM = Uint(5000) +INITIAL_BASE_FEE = Uint(1000000000) EMPTY_OMMER_HASH = keccak256(rlp.encode([])) @@ -63,7 +73,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -250,7 +260,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -268,15 +278,25 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + 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, - ) + expected_base_fee_per_gas = INITIAL_BASE_FEE + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: + # For every block except the first, calculate the base fee per gas + # based on the parent block. + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: raise InvalidBlock if header.timestamp <= parent_header.timestamp: diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index c76584859c..22f5a068e8 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Optional, 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 typing_extensions import TypeAlias + +from ethereum.paris import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -59,6 +62,13 @@ class Header: withdrawals_root: Root +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -72,6 +82,13 @@ class Block: withdrawals: Tuple[Withdrawal, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: @@ -95,3 +112,13 @@ class Receipt: cumulative_gas_used: Uint bloom: Bloom logs: Tuple[Log, ...] + + +def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]: + """ + Returns the `base_fee_per_gas` of the given header, or `None` for headers + without that field. + """ + if isinstance(header, Header): + return header.base_fee_per_gas + return previous_blocks.header_base_fee_per_gas(header) diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index d0347b7d1e..ccc6270304 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -21,9 +21,19 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.paris import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt, Withdrawal +from .blocks import ( + AnyBlock, + AnyHeader, + Block, + Header, + Log, + Receipt, + Withdrawal, + header_base_fee_per_gas, +) from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -55,6 +65,7 @@ ELASTICITY_MULTIPLIER = Uint(2) GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024) GAS_LIMIT_MINIMUM = Uint(5000) +INITIAL_BASE_FEE = Uint(1000000000) EMPTY_OMMER_HASH = keccak256(rlp.encode([])) @@ -64,7 +75,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -254,7 +265,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -272,15 +283,25 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + 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, - ) + expected_base_fee_per_gas = INITIAL_BASE_FEE + parent_base_fee_per_gas = header_base_fee_per_gas(parent_header) + if parent_base_fee_per_gas is not None: + # For every block except the first, calculate the base fee per gas + # based on the parent block. + expected_base_fee_per_gas = calculate_base_fee_per_gas( + header.gas_limit, + parent_header.gas_limit, + parent_header.gas_used, + parent_base_fee_per_gas, + ) + if expected_base_fee_per_gas != header.base_fee_per_gas: raise InvalidBlock if header.timestamp <= parent_header.timestamp: diff --git a/src/ethereum/spurious_dragon/blocks.py b/src/ethereum/spurious_dragon/blocks.py index 713b5aecf5..4c57475f56 100644 --- a/src/ethereum/spurious_dragon/blocks.py +++ b/src/ethereum/spurious_dragon/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.tangerine_whistle import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index cb4d0b0702..95d0fb599b 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -22,9 +22,10 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError +from ethereum.tangerine_whistle import fork as previous_fork from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -60,7 +61,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -184,7 +185,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -202,6 +203,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -507,7 +512,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. diff --git a/src/ethereum/tangerine_whistle/blocks.py b/src/ethereum/tangerine_whistle/blocks.py index 713b5aecf5..29e0e2f748 100644 --- a/src/ethereum/tangerine_whistle/blocks.py +++ b/src/ethereum/tangerine_whistle/blocks.py @@ -9,11 +9,14 @@ chain. """ from dataclasses import dataclass -from typing import Tuple +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 U256, Uint +from typing_extensions import TypeAlias + +from ethereum.dao_fork import blocks as previous_blocks from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -44,6 +47,13 @@ class Header: nonce: Bytes8 +AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header] +""" +Represents all headers that may have appeared in the blockchain before or in +the current fork. +""" + + @slotted_freezable @dataclass class Block: @@ -56,6 +66,13 @@ class Block: ommers: Tuple[Header, ...] +AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] +""" +Represents all blocks that may have appeared in the blockchain before or in the +current fork. +""" + + @slotted_freezable @dataclass class Log: diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index 1419c87b6d..f5b2542c23 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -20,11 +20,12 @@ from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.dao_fork import fork as previous_fork from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError from . import vm -from .blocks import Block, Header, Log, Receipt +from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt from .bloom import logs_bloom from .fork_types import Address, Bloom, Root from .state import ( @@ -59,7 +60,7 @@ class BlockChain: History and current state of the block chain. """ - blocks: List[Block] + blocks: List[AnyBlock] state: State chain_id: U64 @@ -182,7 +183,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(header: AnyHeader, parent_header: AnyHeader) -> None: """ Verifies a block header. @@ -200,6 +201,10 @@ def validate_header(header: Header, parent_header: Header) -> None: parent_header : Parent Header of the header to check for correctness """ + if not isinstance(header, Header): + assert not isinstance(parent_header, Header) + return previous_fork.validate_header(header, parent_header) + if header.timestamp <= parent_header.timestamp: raise InvalidBlock if header.number != parent_header.number + Uint(1): @@ -499,7 +504,7 @@ def apply_body( def validate_ommers( - ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain + ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain ) -> None: """ Validates the ommers mentioned in the block. 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..c737a30ef3 100644 --- a/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py +++ b/src/ethereum_spec_tools/lint/lints/glacier_forks_hygiene.py @@ -24,11 +24,9 @@ # graffiti near the fork block. ("dao_fork", ".fork", "apply_fork"), ("dao_fork", ".fork", "validate_header"), - # There are some differences between london and arrow_glacier - # in terms of how the fork block is handled. - ("arrow_glacier", ".fork", "calculate_base_fee_per_gas"), - ("arrow_glacier", ".fork", "validate_header"), - ("arrow_glacier", ".fork", "INITIAL_BASE_FEE"), + # Arrow Glacier must check the preceding fork when getting the + # `base_fee_per_gas`. + ("arrow_glacier", ".blocks", "header_base_fee_per_gas"), ] From 29b8abf76ff91e899616d365042cfaa821f007d4 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 19 Nov 2024 13:21:41 -0500 Subject: [PATCH 4/5] force forks to deal with old headers --- setup.cfg | 2 +- src/ethereum/arrow_glacier/blocks.py | 2 +- src/ethereum/arrow_glacier/fork.py | 4 ++-- src/ethereum/berlin/blocks.py | 2 +- src/ethereum/berlin/fork.py | 4 ++-- src/ethereum/byzantium/blocks.py | 2 +- src/ethereum/byzantium/fork.py | 4 ++-- src/ethereum/cancun/blocks.py | 2 +- src/ethereum/constantinople/blocks.py | 2 +- src/ethereum/constantinople/fork.py | 4 ++-- src/ethereum/dao_fork/blocks.py | 2 +- src/ethereum/dao_fork/fork.py | 4 ++-- src/ethereum/frontier/blocks.py | 12 +++++++++++- src/ethereum/gray_glacier/blocks.py | 2 +- src/ethereum/gray_glacier/fork.py | 4 ++-- src/ethereum/homestead/blocks.py | 2 ++ src/ethereum/homestead/fork.py | 4 ++-- src/ethereum/istanbul/blocks.py | 2 +- src/ethereum/istanbul/fork.py | 4 ++-- src/ethereum/london/blocks.py | 2 +- src/ethereum/london/fork.py | 4 ++-- src/ethereum/muir_glacier/blocks.py | 2 +- src/ethereum/muir_glacier/fork.py | 4 ++-- src/ethereum/paris/blocks.py | 2 +- src/ethereum/shanghai/blocks.py | 2 +- src/ethereum/spurious_dragon/blocks.py | 2 +- src/ethereum/spurious_dragon/fork.py | 4 ++-- src/ethereum/tangerine_whistle/blocks.py | 2 +- src/ethereum/tangerine_whistle/fork.py | 4 ++-- .../evm_tools/loaders/fork_loader.py | 12 ++++++++++++ tests/helpers/load_state_tests.py | 2 ++ tests/homestead/test_rlp.py | 2 +- 32 files changed, 67 insertions(+), 41 deletions(-) diff --git a/setup.cfg b/setup.cfg index bee1da28ac..c839174a01 100644 --- a/setup.cfg +++ b/setup.cfg @@ -116,7 +116,7 @@ install_requires = typing_extensions>=4 py_ecc @ git+https://github.com/petertdavies/py_ecc.git@127184f4c57b1812da959586d0fe8f43bb1a2389 ethereum-types>=0.2.1,<0.3 - ethereum-rlp>=0.1.1,<0.2 + ethereum-rlp>=0.1.2,<0.2 [options.package_data] ethereum = diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index bbc37b1e9d..fa3450dc9b 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -64,7 +64,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index 2eb045138a..db507f2ca6 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -537,7 +537,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -728,7 +728,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index 5d36c734c8..41ab460c9b 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index 845c77214e..2c41076ba4 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -421,7 +421,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -607,7 +607,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/byzantium/blocks.py b/src/ethereum/byzantium/blocks.py index 7a01dfde6f..5e56c27381 100644 --- a/src/ethereum/byzantium/blocks.py +++ b/src/ethereum/byzantium/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index d18ccfbe28..d07f853ced 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -414,7 +414,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -597,7 +597,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index ff7d8c5ceb..bea3763c6e 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -81,7 +81,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/constantinople/blocks.py b/src/ethereum/constantinople/blocks.py index ca2b2b8673..f4ba5b20d7 100644 --- a/src/ethereum/constantinople/blocks.py +++ b/src/ethereum/constantinople/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index f9d025300c..e4a041fec8 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -414,7 +414,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -597,7 +597,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/dao_fork/blocks.py b/src/ethereum/dao_fork/blocks.py index 541d2b757c..13999d68f3 100644 --- a/src/ethereum/dao_fork/blocks.py +++ b/src/ethereum/dao_fork/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index bf131ff3bd..cef2a3fd18 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -423,7 +423,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> ApplyBodyOutput: """ Executes a block. @@ -603,7 +603,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/frontier/blocks.py b/src/ethereum/frontier/blocks.py index e05d8ffbbe..e8be8d6af4 100644 --- a/src/ethereum/frontier/blocks.py +++ b/src/ethereum/frontier/blocks.py @@ -11,6 +11,7 @@ from dataclasses import dataclass from typing import Tuple +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 @@ -52,6 +53,15 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + """ + # Because frontier is the first fork, we know what type the header is. + return rlp.deserialize_to(Header, raw_header) + + @slotted_freezable @dataclass class Block: @@ -61,7 +71,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Block diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index 2e60d66a3f..a8a0825a4f 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -64,7 +64,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index 49dfd54ba4..466f22bb41 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -537,7 +537,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -728,7 +728,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/homestead/blocks.py b/src/ethereum/homestead/blocks.py index f7063af2a0..1207b14641 100644 --- a/src/ethereum/homestead/blocks.py +++ b/src/ethereum/homestead/blocks.py @@ -11,11 +11,13 @@ 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.frontier import blocks as previous_blocks from ..crypto.hash import Hash32 diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index 4977ef2596..a2afbda5f3 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -405,7 +405,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> ApplyBodyOutput: """ Executes a block. @@ -585,7 +585,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/istanbul/blocks.py b/src/ethereum/istanbul/blocks.py index 84dd6b77e4..5240ce6b7f 100644 --- a/src/ethereum/istanbul/blocks.py +++ b/src/ethereum/istanbul/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index 90d64d8a43..ddf0bcc6e2 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -414,7 +414,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -598,7 +598,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 461a018240..73f3223d6c 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -64,7 +64,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index b90808281b..b333c2e64f 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -537,7 +537,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -728,7 +728,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/muir_glacier/blocks.py b/src/ethereum/muir_glacier/blocks.py index 76a72c924a..c056dbd4c8 100644 --- a/src/ethereum/muir_glacier/blocks.py +++ b/src/ethereum/muir_glacier/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index d68e091c1e..0e9ec31e66 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -414,7 +414,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -598,7 +598,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index 3ac06626f0..d6c308f53c 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -64,7 +64,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index 22f5a068e8..cc8db81330 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -78,7 +78,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/spurious_dragon/blocks.py b/src/ethereum/spurious_dragon/blocks.py index 4c57475f56..baf42e6d13 100644 --- a/src/ethereum/spurious_dragon/blocks.py +++ b/src/ethereum/spurious_dragon/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index 95d0fb599b..40a4d24546 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -410,7 +410,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], chain_id: U64, ) -> ApplyBodyOutput: """ @@ -593,7 +593,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. diff --git a/src/ethereum/tangerine_whistle/blocks.py b/src/ethereum/tangerine_whistle/blocks.py index 29e0e2f748..e703ea2687 100644 --- a/src/ethereum/tangerine_whistle/blocks.py +++ b/src/ethereum/tangerine_whistle/blocks.py @@ -63,7 +63,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[AnyHeader, ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index f5b2542c23..eb34b62670 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -405,7 +405,7 @@ def apply_body( block_time: U256, block_difficulty: Uint, transactions: Tuple[Transaction, ...], - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> ApplyBodyOutput: """ Executes a block. @@ -585,7 +585,7 @@ def pay_rewards( state: State, block_number: Uint, coinbase: Address, - ommers: Tuple[Header, ...], + ommers: Tuple[AnyHeader, ...], ) -> None: """ Pay rewards to the block miner as well as the ommers miners. 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..2876d5ecf3 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -5,6 +5,7 @@ import importlib from typing import Any +from ethereum.fork_criteria import ForkCriteria from ethereum_spec_tools.forks import Hardfork @@ -20,6 +21,17 @@ def __init__(self, fork_module: str): self._fork_module = fork_module self._forks = Hardfork.discover() + @property + def fork_criteria(self) -> ForkCriteria: + """Activation criteria for the loaded fork.""" + mod = importlib.import_module(f"ethereum.{self._fork_module}") + return mod.FORK_CRITERIA + + @fork_criteria.setter + def fork_criteria(self, value: ForkCriteria) -> None: + mod = importlib.import_module(f"ethereum.{self._fork_module}") + mod.FORK_CRITERIA = value # type: ignore[attr-defined] + @property def fork_module(self) -> str: """Module that contains the fork code""" diff --git a/tests/helpers/load_state_tests.py b/tests/helpers/load_state_tests.py index eca1953ae3..6c3bd54f36 100644 --- a/tests/helpers/load_state_tests.py +++ b/tests/helpers/load_state_tests.py @@ -14,6 +14,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.exceptions import EthereumException +from ethereum.fork_criteria import ByBlockNumber from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load @@ -26,6 +27,7 @@ class NoTestsFound(Exception): def run_blockchain_st_test(test_case: Dict, load: Load) -> None: + load.fork.fork_criteria = ByBlockNumber(0) test_file = test_case["test_file"] test_key = test_case["test_key"] diff --git a/tests/homestead/test_rlp.py b/tests/homestead/test_rlp.py index 647ddcafc5..2fdbdfeb9c 100644 --- a/tests/homestead/test_rlp.py +++ b/tests/homestead/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(1150000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), From d5b2b6b620a425c9b4ffae1ed8c376c363986d1a Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 19 Nov 2024 19:31:36 -0500 Subject: [PATCH 5/5] More strict bounds on ommer types --- src/ethereum/arrow_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/berlin/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/byzantium/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/cancun/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/constantinople/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/dao_fork/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/gray_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/homestead/blocks.py | 40 ++++++++++++++++++++-- src/ethereum/istanbul/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/london/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/muir_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/paris/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/shanghai/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/spurious_dragon/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/tangerine_whistle/blocks.py | 42 ++++++++++++++++++++++-- tests/berlin/test_rlp.py | 2 +- tests/byzantium/test_rlp.py | 2 +- tests/cancun/test_rlp.py | 2 +- tests/constantinople/test_rlp.py | 2 +- tests/istanbul/test_rlp.py | 2 +- tests/london/test_rlp.py | 2 +- tests/paris/test_rlp.py | 2 +- tests/shanghai/test_rlp.py | 2 +- tests/spurious_dragon/test_rlp.py | 2 +- tests/tangerine_whistle/test_rlp.py | 2 +- 25 files changed, 608 insertions(+), 40 deletions(-) diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index fa3450dc9b..2027365186 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.london import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -55,6 +57,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index 41ab460c9b..836b0b7948 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.muir_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/byzantium/blocks.py b/src/ethereum/byzantium/blocks.py index 5e56c27381..76690bd846 100644 --- a/src/ethereum/byzantium/blocks.py +++ b/src/ethereum/byzantium/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.spurious_dragon import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index bea3763c6e..7a4bb3ba24 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.shanghai import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -72,6 +74,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -81,7 +119,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/constantinople/blocks.py b/src/ethereum/constantinople/blocks.py index f4ba5b20d7..8d0d38762b 100644 --- a/src/ethereum/constantinople/blocks.py +++ b/src/ethereum/constantinople/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias from ethereum.byzantium import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/dao_fork/blocks.py b/src/ethereum/dao_fork/blocks.py index 13999d68f3..91cd4abb79 100644 --- a/src/ethereum/dao_fork/blocks.py +++ b/src/ethereum/dao_fork/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.homestead import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index a8a0825a4f..120b3e1a20 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias from ethereum.arrow_glacier import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -55,6 +57,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/homestead/blocks.py b/src/ethereum/homestead/blocks.py index 1207b14641..327207bb07 100644 --- a/src/ethereum/homestead/blocks.py +++ b/src/ethereum/homestead/blocks.py @@ -9,7 +9,7 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, Tuple, Union from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes8, Bytes32 @@ -56,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -65,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/istanbul/blocks.py b/src/ethereum/istanbul/blocks.py index 5240ce6b7f..046c8b4162 100644 --- a/src/ethereum/istanbul/blocks.py +++ b/src/ethereum/istanbul/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias from ethereum.constantinople import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 73f3223d6c..2c82dc906b 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias from ethereum.berlin import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -55,6 +57,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/muir_glacier/blocks.py b/src/ethereum/muir_glacier/blocks.py index c056dbd4c8..55669f5745 100644 --- a/src/ethereum/muir_glacier/blocks.py +++ b/src/ethereum/muir_glacier/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.istanbul import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index d6c308f53c..503bb1b689 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.gray_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -55,6 +57,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index cc8db81330..dfb9a24bfb 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, Tuple, Union +from typing import Annotated, Optional, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.paris import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -69,6 +71,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -78,7 +116,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/spurious_dragon/blocks.py b/src/ethereum/spurious_dragon/blocks.py index baf42e6d13..a1a59b3dc4 100644 --- a/src/ethereum/spurious_dragon/blocks.py +++ b/src/ethereum/spurious_dragon/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.tangerine_whistle import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/tangerine_whistle/blocks.py b/src/ethereum/tangerine_whistle/blocks.py index e703ea2687..943de99e63 100644 --- a/src/ethereum/tangerine_whistle/blocks.py +++ b/src/ethereum/tangerine_whistle/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Tuple, Union +from typing import Annotated, 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 typing_extensions import TypeAlias from ethereum.dao_fork import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +def decode_header(raw_header: rlp.Simple) -> AnyHeader: + """ + Convert `raw_header` from raw sequences and bytes to a structured block + header. + + Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs + to this fork, decodes it accordingly. If not, this function forwards to the + preceding fork where the process is repeated. + """ + from . import FORK_CRITERIA + + # First, ensure that `raw_header` is not `bytes` (and is therefore a + # sequence.) + if isinstance(raw_header, bytes): + raise InvalidBlock("header is bytes, expected sequence") + + # Next, extract the block number and timestamp (which are always at index 8 + # and 11 respectively.) + raw_number = raw_header[8] + if not isinstance(raw_number, bytes): + raise InvalidBlock("header number is sequence, expected bytes") + number = Uint.from_be_bytes(raw_number) + + raw_timestamp = raw_header[11] + if not isinstance(raw_timestamp, bytes): + raise InvalidBlock("header timestamp is sequence, expected bytes") + timestamp = U256.from_be_bytes(raw_timestamp) + + # Finally, check if this header belongs to this fork. + if FORK_CRITERIA.check(number, timestamp): + return rlp.deserialize_to(Header, raw_header) + + # If it doesn't, forward to the preceding fork. + return previous_blocks.decode_header(raw_header) + + @slotted_freezable @dataclass class Block: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/tests/berlin/test_rlp.py b/tests/berlin/test_rlp.py index e853f4e577..0ca8842432 100644 --- a/tests/berlin/test_rlp.py +++ b/tests/berlin/test_rlp.py @@ -74,7 +74,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(12244000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/byzantium/test_rlp.py b/tests/byzantium/test_rlp.py index fa0985ac9c..ef9945f3e5 100644 --- a/tests/byzantium/test_rlp.py +++ b/tests/byzantium/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(4370000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/cancun/test_rlp.py b/tests/cancun/test_rlp.py index 6478fe25d9..80ba30408e 100644 --- a/tests/cancun/test_rlp.py +++ b/tests/cancun/test_rlp.py @@ -93,7 +93,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(19426587), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/constantinople/test_rlp.py b/tests/constantinople/test_rlp.py index bdb13cc502..f1b942417a 100644 --- a/tests/constantinople/test_rlp.py +++ b/tests/constantinople/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(7280000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/istanbul/test_rlp.py b/tests/istanbul/test_rlp.py index 3eb39041ac..50df6027f9 100644 --- a/tests/istanbul/test_rlp.py +++ b/tests/istanbul/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(9069000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/london/test_rlp.py b/tests/london/test_rlp.py index a18856e6e7..25fd7cc6ed 100644 --- a/tests/london/test_rlp.py +++ b/tests/london/test_rlp.py @@ -91,7 +91,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(12965000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/paris/test_rlp.py b/tests/paris/test_rlp.py index 33497fde81..6de37432a1 100644 --- a/tests/paris/test_rlp.py +++ b/tests/paris/test_rlp.py @@ -91,7 +91,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(15537394), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/shanghai/test_rlp.py b/tests/shanghai/test_rlp.py index 7a7d689ef6..25e00b4e2e 100644 --- a/tests/shanghai/test_rlp.py +++ b/tests/shanghai/test_rlp.py @@ -93,7 +93,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(17034870), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/spurious_dragon/test_rlp.py b/tests/spurious_dragon/test_rlp.py index 05506ef6f1..87f00fc9a2 100644 --- a/tests/spurious_dragon/test_rlp.py +++ b/tests/spurious_dragon/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(2675000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/tangerine_whistle/test_rlp.py b/tests/tangerine_whistle/test_rlp.py index 3a4a30074a..368dac3127 100644 --- a/tests/tangerine_whistle/test_rlp.py +++ b/tests/tangerine_whistle/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(2463000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5),