From cae1ddd7add6e3dec17ca3d693c0899096171275 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:08:41 +0000 Subject: [PATCH 1/4] fix(tests): size the EIP-6780 collision subcall gas from the fork The monad access and storage repricing overran the hardcoded 100k, so the CREATE2 deployments ran out of gas and the scenarios degraded silently. Co-Authored-By: Claude --- .../test_dynamic_create2_selfdestruct_collision.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) 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 From 2755472b9172703a809bda358edbb56ccba6f309 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:12:14 +0000 Subject: [PATCH 2/4] fix(tests): pin the fee cap of the EIP-7702 pointer reset second block Monad counts the gas limit towards the block gas used, so the automatic gas limits fill block 1 and raise the base fee past the default fee cap. Co-Authored-By: Claude --- tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py | 4 ++++ 1 file changed, 4 insertions(+) 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, From a81b0f5e25963e2587eb6658e60538e5feaa43d9 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:32:29 +0000 Subject: [PATCH 3/4] fix(forks): enable the EIPs of every fork a fork inherits from MONAD_NINE reported only Prague's EIPs, so the EIP-7883 tests priced modexp with the pre-Osaka formula the spec no longer charges. Co-Authored-By: Claude --- .../testing/src/execution_testing/forks/base_fork.py | 9 +++++++++ 1 file changed, 9 insertions(+) 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__ From 6442eb7a7c57d295b11564cbd83f3cd0896f6c19 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:27:42 +0000 Subject: [PATCH 4/4] fix(monad): reject transactions carrying another chain's identifier Port the upstream chain identifier check, which the monad forks never had: typed transactions went unvalidated and legacy ones failed as a bad signature. Co-Authored-By: Claude --- src/ethereum/forks/monad_eight/exceptions.py | 16 ++++++++- src/ethereum/forks/monad_eight/fork.py | 11 +++++- .../forks/monad_eight/transactions.py | 36 ++++++++++++++----- src/ethereum/forks/monad_next/exceptions.py | 16 ++++++++- src/ethereum/forks/monad_next/fork.py | 11 +++++- src/ethereum/forks/monad_next/transactions.py | 36 ++++++++++++++----- src/ethereum/forks/monad_nine/exceptions.py | 16 ++++++++- src/ethereum/forks/monad_nine/fork.py | 11 +++++- src/ethereum/forks/monad_nine/transactions.py | 36 ++++++++++++++----- src/ethereum/forks/monad_ten/exceptions.py | 16 ++++++++- src/ethereum/forks/monad_ten/fork.py | 11 +++++- src/ethereum/forks/monad_ten/transactions.py | 36 ++++++++++++++----- 12 files changed, 208 insertions(+), 44 deletions(-) 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)):