diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 30fe2fee5b3..f842878c42f 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -431,6 +431,15 @@ def __init_subclass__( if base_fork_class != BaseFork: base_fork_class._children.add(cls) cls._enabled_eips |= base_fork_class._enabled_eips + # A fork inheriting from more than one fork enables the union of + # their EIPs; the first base alone leads the lineage above. + for base_class in cls.__bases__: + if ( + issubclass(base_class, BaseFork) + and not base_class.is_eip() + and base_class is not BaseFork + ): + cls._enabled_eips |= base_class._enabled_eips eip_bases = [ base_class for base_class in cls.__bases__ diff --git a/src/ethereum/forks/monad_eight/exceptions.py b/src/ethereum/forks/monad_eight/exceptions.py index e74948cb59a..e11cf09929b 100644 --- a/src/ethereum/forks/monad_eight/exceptions.py +++ b/src/ethereum/forks/monad_eight/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/monad_eight/fork.py b/src/ethereum/forks/monad_eight/fork.py index 82e4b0d06d2..0f02c0d8c28 100644 --- a/src/ethereum/forks/monad_eight/fork.py +++ b/src/ethereum/forks/monad_eight/fork.py @@ -43,6 +43,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -72,6 +73,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -467,7 +469,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance( diff --git a/src/ethereum/forks/monad_eight/transactions.py b/src/ethereum/forks/monad_eight/transactions.py index 04c4b77388c..24bb4880a74 100644 --- a/src/ethereum/forks/monad_eight/transactions.py +++ b/src/ethereum/forks/monad_eight/transactions.py @@ -625,7 +625,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -635,9 +653,9 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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. - This function takes chain_id and a transaction as parameters and returns - the address of the sender of the transaction. It raises an - `InvalidSignatureError` if the signature values (r, s, v) are invalid. + This function takes a transaction as a parameter and returns the address + of the sender of the transaction. It raises an `InvalidSignatureError` if + the signature values (r, s, v) are invalid. """ r, s = tx.r, tx.s if U256(0) >= r or r >= SECP256K1N: @@ -652,14 +670,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/monad_next/exceptions.py b/src/ethereum/forks/monad_next/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/monad_next/exceptions.py +++ b/src/ethereum/forks/monad_next/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/monad_next/fork.py b/src/ethereum/forks/monad_next/fork.py index d743f1169a3..653f05d030f 100644 --- a/src/ethereum/forks/monad_next/fork.py +++ b/src/ethereum/forks/monad_next/fork.py @@ -47,6 +47,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -76,6 +77,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -482,7 +484,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance( diff --git a/src/ethereum/forks/monad_next/transactions.py b/src/ethereum/forks/monad_next/transactions.py index 04c4b77388c..24bb4880a74 100644 --- a/src/ethereum/forks/monad_next/transactions.py +++ b/src/ethereum/forks/monad_next/transactions.py @@ -625,7 +625,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -635,9 +653,9 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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. - This function takes chain_id and a transaction as parameters and returns - the address of the sender of the transaction. It raises an - `InvalidSignatureError` if the signature values (r, s, v) are invalid. + This function takes a transaction as a parameter and returns the address + of the sender of the transaction. It raises an `InvalidSignatureError` if + the signature values (r, s, v) are invalid. """ r, s = tx.r, tx.s if U256(0) >= r or r >= SECP256K1N: @@ -652,14 +670,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/monad_nine/exceptions.py b/src/ethereum/forks/monad_nine/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/monad_nine/exceptions.py +++ b/src/ethereum/forks/monad_nine/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/monad_nine/fork.py b/src/ethereum/forks/monad_nine/fork.py index 068442ac1b1..e7c56f8c815 100644 --- a/src/ethereum/forks/monad_nine/fork.py +++ b/src/ethereum/forks/monad_nine/fork.py @@ -44,6 +44,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -73,6 +74,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -479,7 +481,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance( diff --git a/src/ethereum/forks/monad_nine/transactions.py b/src/ethereum/forks/monad_nine/transactions.py index 04c4b77388c..24bb4880a74 100644 --- a/src/ethereum/forks/monad_nine/transactions.py +++ b/src/ethereum/forks/monad_nine/transactions.py @@ -625,7 +625,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -635,9 +653,9 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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. - This function takes chain_id and a transaction as parameters and returns - the address of the sender of the transaction. It raises an - `InvalidSignatureError` if the signature values (r, s, v) are invalid. + This function takes a transaction as a parameter and returns the address + of the sender of the transaction. It raises an `InvalidSignatureError` if + the signature values (r, s, v) are invalid. """ r, s = tx.r, tx.s if U256(0) >= r or r >= SECP256K1N: @@ -652,14 +670,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/monad_ten/exceptions.py b/src/ethereum/forks/monad_ten/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/monad_ten/exceptions.py +++ b/src/ethereum/forks/monad_ten/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/monad_ten/fork.py b/src/ethereum/forks/monad_ten/fork.py index d743f1169a3..653f05d030f 100644 --- a/src/ethereum/forks/monad_ten/fork.py +++ b/src/ethereum/forks/monad_ten/fork.py @@ -47,6 +47,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -76,6 +77,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -482,7 +484,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance( diff --git a/src/ethereum/forks/monad_ten/transactions.py b/src/ethereum/forks/monad_ten/transactions.py index 04c4b77388c..24bb4880a74 100644 --- a/src/ethereum/forks/monad_ten/transactions.py +++ b/src/ethereum/forks/monad_ten/transactions.py @@ -625,7 +625,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -635,9 +653,9 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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. - This function takes chain_id and a transaction as parameters and returns - the address of the sender of the transaction. It raises an - `InvalidSignatureError` if the signature values (r, s, v) are invalid. + This function takes a transaction as a parameter and returns the address + of the sender of the transaction. It raises an `InvalidSignatureError` if + the signature values (r, s, v) are invalid. """ r, s = tx.r, tx.s if U256(0) >= r or r >= SECP256K1N: @@ -652,14 +670,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: 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") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py index 66d4597204f..9682a5d5b1a 100644 --- a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py +++ b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py @@ -89,9 +89,7 @@ def test_dynamic_create2_selfdestruct_collision( # Constants address_zero = Address(0x00) create2_salt = 1 - subcall_gas = 100_000 - if fork.is_eip_enabled(8037): - subcall_gas = 500_000 + subcall_gas = 100_000 + fork_extra_gas # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct @@ -317,9 +315,7 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( # Constants address_zero = Address(0x00) create2_salt = 1 - subcall_gas = 100_000 - if fork.is_eip_enabled(8037): - subcall_gas = 500_000 + subcall_gas = 100_000 + fork_extra_gas # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct @@ -593,9 +589,7 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # Constants create2_salt = 1 - subcall_gas = 100_000 - if fork.is_eip_enabled(8037): - subcall_gas = 500_000 + subcall_gas = 100_000 + fork_extra_gas # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py index 05bc9cbdef4..2cbe05b6b12 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py @@ -1973,6 +1973,10 @@ def test_pointer_resets_an_empty_code_account_with_storage( value=1000, nonce=6, sender=sender, + # The automatic gas limits fill the first block, which raises the + # base fee for this block on forks that count the gas limit + # rather than the gas used towards the block gas used. + max_fee_per_gas=100, authorization_list=[ AuthorizationTuple( address=newly_created_address,