diff --git a/newsfragments/3659.feature.rst b/newsfragments/3659.feature.rst new file mode 100644 index 0000000000..b34dfdb5c6 --- /dev/null +++ b/newsfragments/3659.feature.rst @@ -0,0 +1 @@ +Support for Prague network upgrade, mainly ``requests_hash`` and ``authorization_list`` formatters. Add support for serializing ``SignedSetCodeTransaction`` (`eth-account` pydantic model) directly added to transaction dicts. diff --git a/newsfragments/3659.internal.rst b/newsfragments/3659.internal.rst new file mode 100644 index 0000000000..49146e985b --- /dev/null +++ b/newsfragments/3659.internal.rst @@ -0,0 +1 @@ +Run each integration test in isolation and parallelize, instead of running them all within a single `geth` (for example) process. This prevents muddied test contexts. diff --git a/setup.py b/setup.py index 74027d53fe..0757810e53 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ "tester": [ # Note: ethereum-maintained libraries in this list should be added to the # `install_pre_releases.py` script. - "eth-tester[py-evm]>=0.12.0b1,<0.13.0b1", + "eth-tester[py-evm]>=0.13.0b1,<0.14.0b1", "py-geth>=5.1.0", ], "dev": [ @@ -67,7 +67,7 @@ # Note: ethereum-maintained libraries in this list should be added to the # `install_pre_releases.py` script. "eth-abi>=5.0.1", - "eth-account>=0.13.1", + "eth-account>=0.13.6", "eth-hash[pycryptodome]>=0.5.1", "eth-typing>=5.0.0", "eth-utils>=5.0.0", diff --git a/tests/conftest.py b/tests/conftest.py index 3e13bd6c30..66ea6dd086 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,21 +19,12 @@ RequestMocker, ) -from .utils import ( - get_open_port, -) - @pytest.fixture(scope="module", params=[lambda x: to_bytes(hexstr=x), identity]) def address_conversion_func(request): return request.param -@pytest.fixture() -def open_port(): - return get_open_port() - - # --- session-scoped constants --- # diff --git a/tests/core/eth-module/test_block_api.py b/tests/core/eth-module/test_block_api.py index e295953181..5479f11591 100644 --- a/tests/core/eth-module/test_block_api.py +++ b/tests/core/eth-module/test_block_api.py @@ -46,6 +46,7 @@ def test_get_block_formatters_with_null_values(w3, request_mocker): "blobGasUsed": None, "excessBlobGas": None, "parentBeaconBlockRoot": None, + "requestsHash": None, } with request_mocker(w3, mock_results={"eth_getBlockByNumber": null_values_block}): received_block = w3.eth.get_block("pending") @@ -109,6 +110,9 @@ def test_get_block_formatters_with_pre_formatted_values(w3, request_mocker): "parentBeaconBlockRoot": ( "0x6470e77f1b8a55a49a57b3f74c2a10a76185636d65122053752ea5e4bb4dac59" ), + "requestsHash": ( + "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ), } with request_mocker( @@ -170,4 +174,5 @@ def test_get_block_formatters_with_pre_formatted_values(w3, request_mocker): "parentBeaconBlockRoot": HexBytes( unformatted_values_block["parentBeaconBlockRoot"] ), + "requestsHash": HexBytes(unformatted_values_block["requestsHash"]), } diff --git a/tests/core/eth-module/test_transactions.py b/tests/core/eth-module/test_transactions.py index 040a309599..d325fd89e2 100644 --- a/tests/core/eth-module/test_transactions.py +++ b/tests/core/eth-module/test_transactions.py @@ -2,6 +2,9 @@ import collections import itertools +from eth_account import ( + Account, +) from eth_utils import ( to_checksum_address, to_int, @@ -9,7 +12,14 @@ from hexbytes import ( HexBytes, ) +import pytest_asyncio +from tests.core.contracts.utils import ( + async_deploy, +) +from web3._utils.contract_sources.contract_data.math_contract import ( + MATH_CONTRACT_DATA, +) from web3._utils.ens import ( ens_addresses, ) @@ -29,6 +39,20 @@ RECEIPT_TIMEOUT = 0.2 +@pytest.fixture +def async_math_contract_factory(async_w3): + return async_w3.eth.contract(**MATH_CONTRACT_DATA) + + +@pytest_asyncio.fixture +async def async_math_contract( + async_w3, async_math_contract_factory, address_conversion_func +): + return await async_deploy( + async_w3, async_math_contract_factory, address_conversion_func + ) + + def _tx_indexing_response_iterator(): while True: yield {"error": {"message": "transaction indexing in progress"}} @@ -420,3 +444,80 @@ async def test_async_send_raw_blob_transaction(async_w3): assert transaction["blobVersionedHashes"][0] == HexBytes( "0x0127c38bcad458d932e828b580b9ad97310be01407dfa0ed88118735980a3e9a" ) + + +@pytest.mark.asyncio +async def test_send_set_code_transaction(async_w3, async_math_contract): + pkey = async_w3.provider.ethereum_tester.backend.account_keys[0] + acct = Account.from_key(pkey) + + nonce = await async_w3.eth.get_transaction_count(acct.address) + chain_id = await async_w3.eth.chain_id + + math_contract_address = ( + f"0x{async_math_contract.address.hex()}" + if isinstance(async_math_contract.address, bytes) + else async_math_contract.address + ) + auth = { + "chainId": chain_id, + "address": math_contract_address, + "nonce": nonce + 1, + } + signed_auth = acct.sign_authorization(auth) + + # get current math counter and increase it only in the delegation by n + math_counter = await async_math_contract.functions.counter().call() + built_tx = await async_math_contract.functions.incrementCounter( + math_counter + 1337 + ).build_transaction({}) + txn = { + "chainId": chain_id, + "to": acct.address, + "value": 0, + "gas": 200_000, + "nonce": nonce, + "maxPriorityFeePerGas": 10**9, + "maxFeePerGas": 10**9, + "data": built_tx["data"], + "authorizationList": [signed_auth], + } + + tx_hash = await async_w3.eth.send_transaction(txn) + get_tx = await async_w3.eth.get_transaction(tx_hash) + await async_w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10) + + code = await async_w3.eth.get_code(acct.address) + + assert code.to_0x_hex().lower() == f"0xef0100{math_contract_address[2:].lower()}" + delegated = async_w3.eth.contract(address=acct.address, abi=async_math_contract.abi) + # assert the math counter is increased by 1337 only in delegated acct + assert await async_math_contract.functions.counter().call() == math_counter + delegated_call = await delegated.functions.counter().call() + assert delegated_call == math_counter + 1337 + + assert len(get_tx["authorizationList"]) == 1 + get_auth = get_tx["authorizationList"][0] + assert get_auth["chainId"] == chain_id + assert get_auth["address"].lower() == math_contract_address.lower() + assert get_auth["nonce"] == nonce + 1 + assert isinstance(get_auth["yParity"], int) + assert isinstance(get_auth["r"], HexBytes) + assert isinstance(get_auth["s"], HexBytes) + + # reset code + reset_auth = { + "chainId": chain_id, + "address": "0x" + ("00" * 20), + "nonce": nonce + 3, + } + signed_reset_auth = acct.sign_authorization(reset_auth) + new_txn = dict(txn) + new_txn["authorizationList"] = [signed_reset_auth] + new_txn["nonce"] = nonce + 2 + + reset_tx_hash = await async_w3.eth.send_transaction(new_txn) + await async_w3.eth.wait_for_transaction_receipt(reset_tx_hash, timeout=10) + + reset_code = await async_w3.eth.get_code(acct.address) + assert reset_code == HexBytes("0x") diff --git a/tests/core/providers/test_legacy_websocket_provider.py b/tests/core/providers/test_legacy_websocket_provider.py index ae4e84b612..46d1884a0b 100644 --- a/tests/core/providers/test_legacy_websocket_provider.py +++ b/tests/core/providers/test_legacy_websocket_provider.py @@ -1,19 +1,5 @@ import pytest -import asyncio -from asyncio.exceptions import ( - TimeoutError, -) -from threading import ( - Thread, -) - -from websockets.legacy.server import ( - serve, -) -from tests.utils import ( - wait_for_ws, -) from web3 import ( Web3, ) @@ -26,39 +12,6 @@ ) -@pytest.fixture -def start_websocket_server(open_port): - event_loop = asyncio.new_event_loop() - - def run_server(): - async def empty_server(websocket, path): - data = await websocket.recv() - await asyncio.sleep(0.02) - await websocket.send(data) - - asyncio.set_event_loop(event_loop) - server = serve(empty_server, "127.0.0.1", open_port) - event_loop.run_until_complete(server) - event_loop.run_forever() - - thd = Thread(target=run_server) - thd.start() - try: - yield - finally: - event_loop.call_soon_threadsafe(event_loop.stop) - - -@pytest.fixture -def w3(open_port, start_websocket_server): - # need new event loop as the one used by server is already running - event_loop = asyncio.new_event_loop() - endpoint_uri = f"ws://127.0.0.1:{open_port}" - event_loop.run_until_complete(wait_for_ws(endpoint_uri)) - provider = LegacyWebSocketProvider(endpoint_uri, websocket_timeout=0.01) - return Web3(provider) - - def test_no_args(): provider = LegacyWebSocketProvider() w3 = Web3(provider) @@ -69,11 +22,6 @@ def test_no_args(): w3.is_connected(show_traceback=True) -def test_websocket_provider_timeout(w3): - with pytest.raises(TimeoutError): - w3.eth.accounts - - def test_restricted_websocket_kwargs(): invalid_kwargs = {"uri": "ws://127.0.0.1:8546"} re_exc_message = f".*found: {set(invalid_kwargs)!r}*" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3cef5b7afb..fe345f45b6 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -19,7 +19,7 @@ ) -@pytest.fixture(scope="module") +@pytest.fixture def math_contract_factory(w3): contract_factory = w3.eth.contract( abi=MATH_CONTRACT_ABI, bytecode=MATH_CONTRACT_BYTECODE @@ -27,7 +27,7 @@ def math_contract_factory(w3): return contract_factory -@pytest.fixture(scope="module") +@pytest.fixture def emitter_contract_factory(w3): contract_factory = w3.eth.contract( abi=EMITTER_CONTRACT_ABI, bytecode=EMITTER_CONTRACT_BYTECODE @@ -35,7 +35,7 @@ def emitter_contract_factory(w3): return contract_factory -@pytest.fixture(scope="module") +@pytest.fixture def revert_contract_factory(w3): contract_factory = w3.eth.contract( abi=REVERT_CONTRACT_ABI, bytecode=REVERT_CONTRACT_BYTECODE @@ -43,7 +43,7 @@ def revert_contract_factory(w3): return contract_factory -@pytest.fixture(scope="module") +@pytest.fixture def offchain_lookup_contract_factory(w3): contract_factory = w3.eth.contract( abi=OFFCHAIN_LOOKUP_ABI, bytecode=OFFCHAIN_LOOKUP_BYTECODE @@ -51,7 +51,7 @@ def offchain_lookup_contract_factory(w3): return contract_factory -@pytest.fixture(scope="module") +@pytest.fixture def async_offchain_lookup_contract_factory(async_w3): contract_factory = async_w3.eth.contract( abi=OFFCHAIN_LOOKUP_ABI, bytecode=OFFCHAIN_LOOKUP_BYTECODE @@ -59,7 +59,7 @@ def async_offchain_lookup_contract_factory(async_w3): return contract_factory -@pytest.fixture(scope="module") +@pytest.fixture def event_loop(): loop = asyncio.get_event_loop_policy().new_event_loop() yield loop diff --git a/tests/integration/go_ethereum/conftest.py b/tests/integration/go_ethereum/conftest.py index bbc96d3946..5679ae4df6 100644 --- a/tests/integration/go_ethereum/conftest.py +++ b/tests/integration/go_ethereum/conftest.py @@ -4,12 +4,13 @@ from pathlib import ( Path, ) +import re import subprocess +import time import zipfile from eth_utils import ( is_dict, - to_text, ) import pytest_asyncio @@ -37,7 +38,7 @@ GETH_FIXTURE_ZIP = "geth-1.15.5-fixture.zip" -@pytest.fixture(scope="module") +@pytest.fixture def geth_binary(): from geth.install import ( get_executable_path, @@ -67,7 +68,7 @@ def absolute_datadir(directory_name): ) -@pytest.fixture(scope="module") +@pytest.fixture def get_geth_version(geth_binary): from geth import ( get_geth_version, @@ -86,7 +87,7 @@ def get_geth_version(geth_binary): return geth_version -@pytest.fixture(scope="module") +@pytest.fixture def base_geth_command_arguments(geth_binary, datadir): return ( geth_binary, @@ -94,7 +95,7 @@ def base_geth_command_arguments(geth_binary, datadir): datadir, "--dev", "--dev.period", - "2", + "1", "--password", os.path.join(datadir, "keystore", "pw.txt"), # in order to raise on underpriced transactions, ``txpool.nolocals`` is now @@ -103,7 +104,7 @@ def base_geth_command_arguments(geth_binary, datadir): ) -@pytest.fixture(scope="module") +@pytest.fixture def geth_zipfile_version(get_geth_version): # TODO: Remove support for 1.13.x in next major version if get_geth_version.major == 1 and get_geth_version.minor in [13, 14, 15]: @@ -111,7 +112,7 @@ def geth_zipfile_version(get_geth_version): raise AssertionError("Unsupported geth version") -@pytest.fixture(scope="module") +@pytest.fixture def datadir(tmpdir_factory, geth_zipfile_version): zipfile_path = absolute_datadir(geth_zipfile_version) base_dir = tmpdir_factory.mktemp("goethereum") @@ -121,144 +122,158 @@ def datadir(tmpdir_factory, geth_zipfile_version): return tmp_datadir -@pytest.fixture(scope="module") +@pytest.fixture def geth_fixture_data(datadir): config_file_path = Path(datadir) / "config.json" return json.loads(config_file_path.read_text()) -@pytest.fixture(scope="module") +@pytest.fixture def genesis_file(datadir): genesis_file_path = os.path.join(datadir, "genesis.json") return genesis_file_path -@pytest.fixture(scope="module") -def geth_process(geth_binary, datadir, genesis_file, geth_command_arguments): - init_datadir_command = ( +def wait_for_port(proc, timeout=10): + start = time.time() + wait_time = start + timeout + + while time.time() < wait_time: + line = proc.stderr.readline() + if not line: + continue + + if match := re.compile(r"127\.0\.0\.1:(\d+)").search(line): + port = int(match.group(1)) + if port not in {0, 80}: + return port + + raise TimeoutError(f"Did not find port in logs within {timeout} seconds") + + +@pytest.fixture +def start_geth_process_and_yield_port( + geth_binary, datadir, genesis_file, geth_command_arguments +): + init_cmd = ( geth_binary, "--datadir", str(datadir), "init", str(genesis_file), ) - subprocess.check_output( - init_datadir_command, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + subprocess.check_output(init_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) proc = subprocess.Popen( geth_command_arguments, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, + bufsize=1, ) - try: - yield proc - finally: - kill_proc_gracefully(proc) - output, errors = proc.communicate() - print( - "Geth Process Exited:\n" - f"stdout:{to_text(output)}\n\n" - f"stderr:{to_text(errors)}\n\n" - ) + + port = wait_for_port(proc) + yield port + + kill_proc_gracefully(proc) + output, errors = proc.communicate(timeout=5) + print("Geth Process Exited:\n" f"stdout: {output}\n\n" f"stderr: {errors}\n\n") -@pytest.fixture(scope="module") +@pytest.fixture def math_contract_deploy_txn_hash(geth_fixture_data): return geth_fixture_data["math_deploy_txn_hash"] -@pytest.fixture(scope="module") +@pytest.fixture def math_contract(math_contract_factory, geth_fixture_data): return math_contract_factory(address=geth_fixture_data["math_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def math_contract_address(math_contract, address_conversion_func): return address_conversion_func(math_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def emitter_contract(emitter_contract_factory, geth_fixture_data): return emitter_contract_factory(address=geth_fixture_data["emitter_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def emitter_contract_address(emitter_contract, address_conversion_func): return address_conversion_func(emitter_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def keyfile_account_pkey(geth_fixture_data): return geth_fixture_data["keyfile_account_pkey"] -@pytest.fixture(scope="module") +@pytest.fixture def keyfile_account_address(geth_fixture_data): return geth_fixture_data["keyfile_account_address"] -@pytest.fixture(scope="module") +@pytest.fixture def keyfile_account_address_dual_type(keyfile_account_address, address_conversion_func): yield keyfile_account_address -@pytest.fixture(scope="module") +@pytest.fixture def empty_block(w3, geth_fixture_data): block = w3.eth.get_block(geth_fixture_data["empty_block_hash"]) assert is_dict(block) return block -@pytest.fixture(scope="module") +@pytest.fixture def block_with_txn(w3, geth_fixture_data): block = w3.eth.get_block(geth_fixture_data["block_with_txn_hash"]) assert is_dict(block) return block -@pytest.fixture(scope="module") +@pytest.fixture def mined_txn_hash(geth_fixture_data): return geth_fixture_data["mined_txn_hash"] -@pytest.fixture(scope="module") +@pytest.fixture def block_with_txn_with_log(w3, geth_fixture_data): block = w3.eth.get_block(geth_fixture_data["block_hash_with_log"]) assert is_dict(block) return block -@pytest.fixture(scope="module") +@pytest.fixture def txn_hash_with_log(geth_fixture_data): return geth_fixture_data["txn_hash_with_log"] -@pytest.fixture(scope="module") +@pytest.fixture def block_hash_revert_no_msg(geth_fixture_data): return geth_fixture_data["block_hash_revert_no_msg"] -@pytest.fixture(scope="module") +@pytest.fixture def block_hash_revert_with_msg(geth_fixture_data): return geth_fixture_data["block_hash_revert_with_msg"] -@pytest.fixture(scope="module") +@pytest.fixture def revert_contract(revert_contract_factory, geth_fixture_data): return revert_contract_factory(address=geth_fixture_data["revert_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def offchain_lookup_contract(offchain_lookup_contract_factory, geth_fixture_data): return offchain_lookup_contract_factory( address=geth_fixture_data["offchain_lookup_address"] ) -@pytest.fixture(scope="module") +@pytest.fixture def panic_errors_contract( w3, geth_fixture_data, @@ -267,7 +282,7 @@ def panic_errors_contract( return contract_factory(address=geth_fixture_data["panic_errors_contract_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def storage_contract( w3, geth_fixture_data, @@ -279,19 +294,19 @@ def storage_contract( # --- async --- # -@pytest_asyncio.fixture(scope="module") +@pytest_asyncio.fixture async def async_keyfile_account_address(geth_fixture_data): return geth_fixture_data["keyfile_account_address"] -@pytest_asyncio.fixture(scope="module") +@pytest_asyncio.fixture async def async_keyfile_account_address_dual_type( async_keyfile_account_address, address_conversion_func ): yield async_keyfile_account_address -@pytest.fixture(scope="module") +@pytest.fixture def async_offchain_lookup_contract( async_offchain_lookup_contract_factory, geth_fixture_data ): @@ -300,7 +315,7 @@ def async_offchain_lookup_contract( ) -@pytest.fixture(scope="module") +@pytest.fixture def async_panic_errors_contract( async_w3, geth_fixture_data, @@ -309,35 +324,35 @@ def async_panic_errors_contract( return contract_factory(address=geth_fixture_data["panic_errors_contract_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def async_emitter_contract(async_w3, geth_fixture_data): contract_factory = async_w3.eth.contract(**EMITTER_CONTRACT_DATA) return contract_factory(address=geth_fixture_data["emitter_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def async_emitter_contract_address(async_emitter_contract, address_conversion_func): return address_conversion_func(async_emitter_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def async_math_contract(async_w3, geth_fixture_data): contract_factory = async_w3.eth.contract(**MATH_CONTRACT_DATA) return contract_factory(address=geth_fixture_data["math_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def async_math_contract_address(async_math_contract, address_conversion_func): return address_conversion_func(async_math_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def async_revert_contract(async_w3, geth_fixture_data): contract_factory = async_w3.eth.contract(**REVERT_CONTRACT_DATA) return contract_factory(address=geth_fixture_data["revert_address"]) -@pytest.fixture(scope="module") +@pytest.fixture def async_storage_contract( async_w3, geth_fixture_data, @@ -346,21 +361,21 @@ def async_storage_contract( return contract_factory(address=geth_fixture_data["storage_contract_address"]) -@pytest_asyncio.fixture(scope="module") +@pytest_asyncio.fixture async def async_empty_block(async_w3, geth_fixture_data): block = await async_w3.eth.get_block(geth_fixture_data["empty_block_hash"]) assert is_dict(block) return block -@pytest_asyncio.fixture(scope="module") +@pytest_asyncio.fixture async def async_block_with_txn(async_w3, geth_fixture_data): block = await async_w3.eth.get_block(geth_fixture_data["block_with_txn_hash"]) assert is_dict(block) return block -@pytest_asyncio.fixture(scope="module") +@pytest_asyncio.fixture async def async_block_with_txn_with_log(async_w3, geth_fixture_data): block = await async_w3.eth.get_block(geth_fixture_data["block_hash_with_log"]) assert is_dict(block) diff --git a/tests/integration/go_ethereum/test_goethereum_http.py b/tests/integration/go_ethereum/test_goethereum_http.py index 0415b27a9a..195dc6189d 100644 --- a/tests/integration/go_ethereum/test_goethereum_http.py +++ b/tests/integration/go_ethereum/test_goethereum_http.py @@ -5,9 +5,6 @@ ) import pytest_asyncio -from tests.utils import ( - get_open_port, -) from web3 import ( AsyncWeb3, Web3, @@ -32,29 +29,15 @@ GoEthereumTxPoolModuleTest, GoEthereumWeb3ModuleTest, ) -from .utils import ( - wait_for_aiohttp, - wait_for_http, -) - - -@pytest.fixture(scope="module") -def rpc_port(): - return get_open_port() -@pytest.fixture(scope="module") -def endpoint_uri(rpc_port): - return f"http://localhost:{rpc_port}" - - -def _geth_command_arguments(rpc_port, base_geth_command_arguments, geth_version): +def _geth_command_arguments(base_geth_command_arguments, geth_version): yield from base_geth_command_arguments if geth_version.major == 1: yield from ( "--http", "--http.port", - rpc_port, + "0", "--http.api", "admin,debug,eth,net,web3,txpool", "--ipcdisable", @@ -63,27 +46,29 @@ def _geth_command_arguments(rpc_port, base_geth_command_arguments, geth_version) raise AssertionError("Unsupported Geth version") -@pytest.fixture(scope="module") -def geth_command_arguments(rpc_port, base_geth_command_arguments, get_geth_version): - return _geth_command_arguments( - rpc_port, base_geth_command_arguments, get_geth_version - ) +@pytest.fixture +def geth_command_arguments(base_geth_command_arguments, get_geth_version): + return _geth_command_arguments(base_geth_command_arguments, get_geth_version) -@pytest.fixture(scope="module") -def w3(geth_process, endpoint_uri): - wait_for_http(endpoint_uri) - return Web3(Web3.HTTPProvider(endpoint_uri, request_kwargs={"timeout": 10})) - +@pytest.fixture +def w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port + _w3 = Web3( + Web3.HTTPProvider(f"http://127.0.0.1:{port}", request_kwargs={"timeout": 10}) + ) + return _w3 -@pytest.fixture(scope="module") -def auto_w3(geth_process, endpoint_uri): - wait_for_http(endpoint_uri) +@pytest.fixture +def auto_w3(start_geth_process_and_yield_port, monkeypatch): from web3.auto import ( w3, ) + port = start_geth_process_and_yield_port + monkeypatch.setenv("WEB3_PROVIDER_URI", f"http://127.0.0.1:{port}") + return w3 @@ -116,13 +101,7 @@ class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest): class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest): - def test_auto_provider_batching( - self, - auto_w3: "Web3", - monkeypatch, - endpoint_uri, - ) -> None: - monkeypatch.setenv("WEB3_PROVIDER_URI", endpoint_uri) + def test_auto_provider_batching(self, auto_w3: "Web3") -> None: # test that batch_requests doesn't error out when using the auto provider auto_w3.batch_requests() @@ -138,12 +117,15 @@ class TestGoEthereumTxPoolModuleTest(GoEthereumTxPoolModuleTest): # -- async -- # -@pytest_asyncio.fixture(scope="module") -async def async_w3(geth_process, endpoint_uri): - await wait_for_aiohttp(endpoint_uri) +@pytest_asyncio.fixture +async def async_w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port _w3 = AsyncWeb3( - AsyncHTTPProvider(endpoint_uri, request_kwargs={"timeout": ClientTimeout(10)}) + AsyncHTTPProvider( + f"http://127.0.0.1:{port}", request_kwargs={"timeout": ClientTimeout(10)} + ) ) + await _w3.provider.disconnect() return _w3 diff --git a/tests/integration/go_ethereum/test_goethereum_ipc.py b/tests/integration/go_ethereum/test_goethereum_ipc.py index c495f5feac..7e52633cb4 100644 --- a/tests/integration/go_ethereum/test_goethereum_ipc.py +++ b/tests/integration/go_ethereum/test_goethereum_ipc.py @@ -38,12 +38,12 @@ def _geth_command_arguments(geth_ipc_path, base_geth_command_arguments): ) -@pytest.fixture(scope="module") +@pytest.fixture def geth_command_arguments(geth_ipc_path, base_geth_command_arguments): return _geth_command_arguments(geth_ipc_path, base_geth_command_arguments) -@pytest.fixture(scope="module") +@pytest.fixture def geth_ipc_path(datadir): geth_ipc_dir_path = tempfile.mkdtemp() _geth_ipc_path = os.path.join(geth_ipc_dir_path, "geth.ipc") @@ -53,19 +53,20 @@ def geth_ipc_path(datadir): os.remove(_geth_ipc_path) -@pytest.fixture(scope="module") -def auto_w3(geth_process, geth_ipc_path): - wait_for_socket(geth_ipc_path) - +@pytest.fixture +def auto_w3(start_geth_process_and_yield_port, geth_ipc_path, monkeypatch): from web3.auto import ( w3, ) + wait_for_socket(geth_ipc_path) + monkeypatch.setenv("WEB3_PROVIDER_URI", f"file:///{geth_ipc_path}") + return w3 -@pytest.fixture(scope="module") -def w3(geth_process, geth_ipc_path): +@pytest.fixture +def w3(start_geth_process_and_yield_port, geth_ipc_path): wait_for_socket(geth_ipc_path) return Web3(Web3.IPCProvider(geth_ipc_path, timeout=10)) @@ -79,13 +80,7 @@ class TestGoEthereumDebugModuleTest(GoEthereumDebugModuleTest): class TestGoEthereumEthModuleTest(GoEthereumEthModuleTest): - def test_auto_provider_batching( - self, - auto_w3: "Web3", - monkeypatch, - geth_ipc_path, - ) -> None: - monkeypatch.setenv("WEB3_PROVIDER_URI", f"file:///{geth_ipc_path}") + def test_auto_provider_batching(self, auto_w3: "Web3") -> None: # test that batch_requests doesn't error out when using the auto provider auto_w3.batch_requests() @@ -117,8 +112,8 @@ def test_admin_start_stop_ws(self, w3: "Web3") -> None: # -- async -- # -@pytest_asyncio.fixture(scope="module") -async def async_w3(geth_process, geth_ipc_path): +@pytest_asyncio.fixture +async def async_w3(start_geth_process_and_yield_port, geth_ipc_path): await wait_for_async_socket(geth_ipc_path) async with AsyncWeb3(AsyncIPCProvider(geth_ipc_path, request_timeout=10)) as _aw3: yield _aw3 diff --git a/tests/integration/go_ethereum/test_goethereum_legacy_ws.py b/tests/integration/go_ethereum/test_goethereum_legacy_ws.py index e979159a92..31f7c1914a 100644 --- a/tests/integration/go_ethereum/test_goethereum_legacy_ws.py +++ b/tests/integration/go_ethereum/test_goethereum_legacy_ws.py @@ -1,14 +1,9 @@ import pytest -import asyncio from tests.integration.common import ( COINBASE, MiscWebSocketTest, ) -from tests.utils import ( - get_open_port, - wait_for_ws, -) from web3 import ( Web3, ) @@ -22,17 +17,7 @@ ) -@pytest.fixture(scope="module") -def ws_port(): - return get_open_port() - - -@pytest.fixture(scope="module") -def endpoint_uri(ws_port): - return f"ws://localhost:{ws_port}" - - -def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): +def _geth_command_arguments(base_geth_command_arguments, geth_version): yield from base_geth_command_arguments if geth_version.major == 1: yield from ( @@ -40,7 +25,7 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): COINBASE[2:], "--ws", "--ws.port", - ws_port, + "0", "--ws.api", "admin,debug,eth,net,web3", "--ws.origins", @@ -55,19 +40,17 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): raise AssertionError("Unsupported Geth version") -@pytest.fixture(scope="module") +@pytest.fixture def geth_command_arguments( - geth_binary, get_geth_version, datadir, ws_port, base_geth_command_arguments + geth_binary, get_geth_version, datadir, base_geth_command_arguments ): - return _geth_command_arguments( - ws_port, base_geth_command_arguments, get_geth_version - ) + return _geth_command_arguments(base_geth_command_arguments, get_geth_version) -@pytest.fixture(scope="module") -def w3(geth_process, endpoint_uri): - event_loop = asyncio.new_event_loop() - event_loop.run_until_complete(wait_for_ws(endpoint_uri)) +@pytest.fixture +def w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port + endpoint_uri = f"ws://127.0.0.1:{port}" _w3 = Web3(Web3.LegacyWebSocketProvider(endpoint_uri, websocket_timeout=30)) return _w3 diff --git a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py index 7f9d684145..007aaed0ab 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/conftest.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/conftest.py @@ -3,22 +3,9 @@ from tests.integration.common import ( COINBASE, ) -from tests.utils import ( - get_open_port, -) - - -@pytest.fixture(scope="module") -def ws_port(): - return get_open_port() - - -@pytest.fixture(scope="module") -def endpoint_uri(ws_port): - return f"ws://localhost:{ws_port}" -def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): +def _geth_command_arguments(base_geth_command_arguments, geth_version): yield from base_geth_command_arguments if geth_version.major == 1: yield from ( @@ -26,7 +13,7 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): COINBASE[2:], "--ws", "--ws.port", - ws_port, + "0", "--ws.api", "admin,debug,eth,net,web3", "--ws.origins", @@ -40,10 +27,8 @@ def _geth_command_arguments(ws_port, base_geth_command_arguments, geth_version): raise AssertionError("Unsupported Geth version") -@pytest.fixture(scope="module") +@pytest.fixture def geth_command_arguments( - geth_binary, get_geth_version, datadir, ws_port, base_geth_command_arguments + geth_binary, get_geth_version, datadir, base_geth_command_arguments ): - return _geth_command_arguments( - ws_port, base_geth_command_arguments, get_geth_version - ) + return _geth_command_arguments(base_geth_command_arguments, get_geth_version) diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py index e491d92f37..ce5bffa35d 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_await_w3.py @@ -19,16 +19,17 @@ GoEthereumAsyncNetModuleTest, GoEthereumAsyncWeb3ModuleTest, ) -from ..utils import ( - wait_for_aiohttp, -) -@pytest_asyncio.fixture(scope="module") -async def async_w3(geth_process, endpoint_uri): - await wait_for_aiohttp(endpoint_uri) +@pytest_asyncio.fixture +async def async_w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port # await the persistent connection itself - return await AsyncWeb3(WebSocketProvider(endpoint_uri, request_timeout=10)) + _async_w3 = await AsyncWeb3( + WebSocketProvider(f"ws://127.0.0.1:{port}", request_timeout=10) + ) + yield _async_w3 + await _async_w3.provider.disconnect() class TestGoEthereumAsyncWeb3ModuleTest(GoEthereumAsyncWeb3ModuleTest): diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py index 7187dbf4c3..f07723f4b8 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_ctx_manager_w3.py @@ -19,16 +19,15 @@ GoEthereumAsyncNetModuleTest, GoEthereumAsyncWeb3ModuleTest, ) -from ..utils import ( - wait_for_aiohttp, -) -@pytest_asyncio.fixture(scope="module") -async def async_w3(geth_process, endpoint_uri): - await wait_for_aiohttp(endpoint_uri) +@pytest_asyncio.fixture +async def async_w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port # async context manager pattern - async with AsyncWeb3(WebSocketProvider(endpoint_uri, request_timeout=10)) as w3: + async with AsyncWeb3( + WebSocketProvider(f"ws://127.0.0.1:{port}", request_timeout=10) + ) as w3: yield w3 diff --git a/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py b/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py index 5e80702d1e..2994ada15c 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py +++ b/tests/integration/go_ethereum/test_goethereum_ws/test_async_iterator_w3.py @@ -19,14 +19,12 @@ GoEthereumAsyncNetModuleTest, GoEthereumAsyncWeb3ModuleTest, ) -from ..utils import ( - wait_for_aiohttp, -) -@pytest_asyncio.fixture(scope="module") -async def async_w3(geth_process, endpoint_uri): - await wait_for_aiohttp(endpoint_uri) +@pytest_asyncio.fixture +async def async_w3(start_geth_process_and_yield_port): + port = start_geth_process_and_yield_port + endpoint_uri = f"ws://127.0.0.1:{port}" # async iterator pattern async for w3 in AsyncWeb3(WebSocketProvider(endpoint_uri, request_timeout=10)): return w3 diff --git a/tests/integration/go_ethereum/utils.py b/tests/integration/go_ethereum/utils.py index a9842a8131..2cf92915b7 100644 --- a/tests/integration/go_ethereum/utils.py +++ b/tests/integration/go_ethereum/utils.py @@ -3,9 +3,6 @@ import socket import time -import aiohttp -import requests - def wait_for_socket(ipc_path, timeout=30): start = time.time() @@ -32,29 +29,6 @@ async def wait_for_async_socket(ipc_path, timeout=30): break -def wait_for_http(endpoint_uri, timeout=60): - start = time.time() - while time.time() < start + timeout: - try: - requests.get(endpoint_uri) - except requests.ConnectionError: - time.sleep(0.01) - else: - break - - -async def wait_for_aiohttp(endpoint_uri, timeout=60): - start = time.time() - while time.time() < start + timeout: - try: - async with aiohttp.ClientSession() as session: - await session.get(endpoint_uri) - except aiohttp.client_exceptions.ClientConnectorError: - time.sleep(0.01) - else: - break - - def wait_for_popen(proc, timeout): start = time.time() while time.time() < start + timeout: diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index 2664b236ed..153c66cfbb 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -68,13 +68,13 @@ def _deploy_contract(w3, contract_factory): return contract_factory(contract_address) -@pytest.fixture(scope="module") +@pytest.fixture def eth_tester(): _eth_tester = EthereumTester() return _eth_tester -@pytest.fixture(scope="module") +@pytest.fixture def eth_tester_provider(eth_tester): provider = EthereumTesterProvider(eth_tester) return provider @@ -96,7 +96,7 @@ def _eth_tester_state_setup(w3): ) -@pytest.fixture(scope="module") +@pytest.fixture def w3(eth_tester_provider): _w3 = Web3(eth_tester_provider) _w3.eth.default_account = _w3.eth.accounts[0] @@ -104,7 +104,7 @@ def w3(eth_tester_provider): return _w3 -@pytest.fixture(scope="module") +@pytest.fixture def math_contract_deploy_txn_hash(w3, math_contract_factory): deploy_txn_hash = math_contract_factory.constructor().transact( {"from": w3.eth.default_account} @@ -112,7 +112,7 @@ def math_contract_deploy_txn_hash(w3, math_contract_factory): return deploy_txn_hash -@pytest.fixture(scope="module") +@pytest.fixture def math_contract(w3, math_contract_factory, math_contract_deploy_txn_hash): deploy_receipt = w3.eth.wait_for_transaction_receipt(math_contract_deploy_txn_hash) assert is_dict(deploy_receipt) @@ -121,28 +121,28 @@ def math_contract(w3, math_contract_factory, math_contract_deploy_txn_hash): return math_contract_factory(contract_address) -@pytest.fixture(scope="module") +@pytest.fixture def math_contract_address(math_contract, address_conversion_func): return address_conversion_func(math_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def storage_contract(w3): contract_factory = w3.eth.contract(**STORAGE_CONTRACT_DATA) return _deploy_contract(w3, contract_factory) -@pytest.fixture(scope="module") +@pytest.fixture def emitter_contract(w3, emitter_contract_factory): return _deploy_contract(w3, emitter_contract_factory) -@pytest.fixture(scope="module") +@pytest.fixture def emitter_contract_address(emitter_contract, address_conversion_func): return address_conversion_func(emitter_contract.address) -@pytest.fixture(scope="module") +@pytest.fixture def empty_block(w3): w3.testing.mine() block = w3.eth.get_block("latest") @@ -150,7 +150,7 @@ def empty_block(w3): return block -@pytest.fixture(scope="module") +@pytest.fixture def block_with_txn(w3): txn_hash = w3.eth.send_transaction( { @@ -168,12 +168,12 @@ def block_with_txn(w3): return block -@pytest.fixture(scope="module") +@pytest.fixture def mined_txn_hash(block_with_txn): return block_with_txn["transactions"][0] -@pytest.fixture(scope="module") +@pytest.fixture def block_with_txn_with_log(w3, emitter_contract): txn_hash = emitter_contract.functions.logDouble( which=EMITTER_ENUM["LogDoubleWithIndex"], @@ -185,12 +185,12 @@ def block_with_txn_with_log(w3, emitter_contract): return block -@pytest.fixture(scope="module") +@pytest.fixture def txn_hash_with_log(block_with_txn_with_log): return block_with_txn_with_log["transactions"][0] -@pytest.fixture(scope="module") +@pytest.fixture def revert_contract(w3, revert_contract_factory): return _deploy_contract(w3, revert_contract_factory) @@ -198,23 +198,23 @@ def revert_contract(w3, revert_contract_factory): # # Offchain Lookup Contract Setup # -@pytest.fixture(scope="module") +@pytest.fixture def offchain_lookup_contract(w3, offchain_lookup_contract_factory): return _deploy_contract(w3, offchain_lookup_contract_factory) -@pytest.fixture(scope="module") +@pytest.fixture def panic_errors_contract(w3): panic_errors_contract_factory = w3.eth.contract(**PANIC_ERRORS_CONTRACT_DATA) return _deploy_contract(w3, panic_errors_contract_factory) -@pytest.fixture(scope="module") +@pytest.fixture def keyfile_account_pkey(): yield KEYFILE_ACCOUNT_PKEY -@pytest.fixture(scope="module") +@pytest.fixture def keyfile_account_address(): yield KEYFILE_ACCOUNT_ADDRESS diff --git a/tests/utils.py b/tests/utils.py index 3aac726ba3..4779d33658 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,9 @@ import socket import time +from websockets import ( + WebSocketException, +) from websockets.legacy.client import ( connect, ) @@ -45,12 +48,24 @@ async def wait_for_ws(endpoint_uri, timeout=10): start = time.time() while time.time() < start + timeout: try: - async with connect(uri=endpoint_uri): - pass - except OSError: - await asyncio.sleep(0.01) - else: - break + async with connect(uri=endpoint_uri) as ws: + await ws.send( + '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' + ) + r = await ws.recv() + if isinstance(r, bytes): + r = r.decode("utf-8") + + if "geth" in r.lower(): + return + except (OSError, WebSocketException): + pass + except Exception: + raise + await asyncio.sleep(0.01) + raise TimeoutError( + f"Geth WebSocket did not respond on {endpoint_uri} within {timeout} seconds." + ) async def _async_wait_for_block_fixture_logic(async_w3, block_number=1, timeout=None): diff --git a/tox.ini b/tox.ini index b02cbd69b4..3054189bbc 100644 --- a/tox.ini +++ b/tox.ini @@ -20,17 +20,18 @@ allowlist_externals=make,pre-commit install_command=python -m pip install {opts} {packages} usedevelop=True commands= + # Note: `pytest-xdist` n=10 seems to cause connection issues with geth; keep it at 6 for now core: pytest {posargs:tests/core -m "not asyncio"} core_async: pytest {posargs:tests/core -m asyncio} - ens: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py} - ensip15: pytest {posargs:tests/ens/normalization/test_normalize_name_ensip15.py -q} - integration-goethereum-ipc: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k "not Async"} - integration-goethereum-ipc_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k Async} - integration-goethereum-http: pytest {posargs:tests/integration/go_ethereum/test_goethereum_http.py -k "not Async"} - integration-goethereum-http_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_http.py -k Async} - integration-goethereum-legacy_ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_legacy_ws.py} - integration-goethereum-ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ws} - integration-ethtester: pytest {posargs:tests/integration/test_ethereum_tester.py} + ens: pytest {posargs:tests/ens --ignore=tests/ens/normalization/test_normalize_name_ensip15.py -n 10} + ensip15: pytest {posargs:tests/ens/normalization/test_normalize_name_ensip15.py -q -n 10} + integration-goethereum-ipc: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k "not Async" -n 10} + integration-goethereum-ipc_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ipc.py -k Async -n 10} + integration-goethereum-http: pytest {posargs:tests/integration/go_ethereum/test_goethereum_http.py -k "not Async" -n 10} + integration-goethereum-http_async: pytest {posargs:tests/integration/go_ethereum/test_goethereum_http.py -k Async -n 10} + integration-goethereum-legacy_ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_legacy_ws.py -n 10} + integration-goethereum-ws: pytest {posargs:tests/integration/go_ethereum/test_goethereum_ws -n 10} + integration-ethtester: pytest {posargs:tests/integration/test_ethereum_tester.py -n 10} docs: make check-docs-ci deps = .[test] diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py index 74d13b8cb2..b08e887ff2 100644 --- a/web3/_utils/encoding.py +++ b/web3/_utils/encoding.py @@ -36,6 +36,9 @@ from hexbytes import ( HexBytes, ) +from pydantic import ( + BaseModel, +) from web3._utils.abi import ( is_address_type, @@ -299,6 +302,11 @@ def default(self, obj: Any) -> Union[Dict[Any, Any], HexStr]: return obj.__dict__ elif isinstance(obj, (HexBytes, bytes)): return to_hex(obj) + elif isinstance(obj, BaseModel): + # TODO: For now we can assume all BaseModel objects behave this way, but + # internally we will start to use the CamelModel from eth-utils. Perhaps + # we should check for that type instead. + return obj.model_dump(by_alias=True) return json.JSONEncoder.default(self, obj) diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py index 71f27e1970..09444811c7 100644 --- a/web3/_utils/method_formatters.py +++ b/web3/_utils/method_formatters.py @@ -47,6 +47,9 @@ from hexbytes import ( HexBytes, ) +from pydantic import ( + BaseModel, +) from web3._utils.abi import ( is_length, @@ -161,6 +164,12 @@ def type_aware_apply_formatters_to_dict( """ Preserve ``AttributeDict`` types if original ``value`` was an ``AttributeDict``. """ + # TODO: In v8, Use eth-utils 5.3.0 as lower pin where ``apply_formatters_to_dict`` + # already handles the CamelModel case, rather than generalizing to all BaseModel + # instances. + if isinstance(value, BaseModel): + value = value.model_dump(by_alias=True) + formatted_dict: Dict[str, Any] = apply_formatters_to_dict(formatters, dict(value)) return ( AttributeDict.recursive(formatted_dict) @@ -177,6 +186,9 @@ def type_aware_apply_formatters_to_dict_keys_and_values( """ Preserve ``AttributeDict`` types if original ``value`` was an ``AttributeDict``. """ + if isinstance(dict_like_object, BaseModel): + dict_like_object = dict_like_object.model_dump(by_alias=True) + formatted_dict = { key_formatters(k): value_formatters(v) for k, v in dict_like_object.items() } @@ -223,6 +235,22 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: } ) +AUTH_LIST_RESULT_FORMATTER = apply_formatter_if( + is_not_null, + apply_formatter_to_array( + type_aware_apply_formatters_to_dict( + { + "chainId": to_integer_if_hex, + "address": to_checksum_address, + "nonce": to_integer_if_hex, + "yParity": to_integer_if_hex, + "r": to_hexbytes(32, variable_length=True), + "s": to_hexbytes(32, variable_length=True), + } + ), + ), +) + TRANSACTION_RESULT_FORMATTERS = { "blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)), "blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex), @@ -255,6 +283,7 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: "blobVersionedHashes": apply_formatter_if( is_not_null, apply_formatter_to_array(to_hexbytes(32)) ), + "authorizationList": AUTH_LIST_RESULT_FORMATTER, } @@ -332,6 +361,7 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: "transactionsRoot": to_hex_if_bytes, "withdrawalsRoot": to_hex_if_bytes, "parentBeaconBlockRoot": to_hex_if_bytes, + "requestsHash": to_hex_if_bytes, } block_request_formatter = type_aware_apply_formatters_to_dict(BLOCK_REQUEST_FORMATTERS) @@ -374,6 +404,7 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: "blobGasUsed": to_integer_if_hex, "excessBlobGas": to_integer_if_hex, "parentBeaconBlockRoot": apply_formatter_if(is_not_null, to_hexbytes(32)), + "requestsHash": apply_formatter_if(is_not_null, to_hexbytes(32)), } block_result_formatter = type_aware_apply_formatters_to_dict(BLOCK_RESULT_FORMATTERS) @@ -466,6 +497,22 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: ) ) +AUTH_LIST_REQUEST_FORMATTER = apply_formatter_if( + is_not_null, + apply_formatter_to_array( + type_aware_apply_formatters_to_dict( + { + "chainId": to_hex_if_integer, + "address": to_checksum_address, + "nonce": to_hex_if_integer, + "yParity": to_hex_if_integer, + "r": to_hex_if_integer, + "s": to_hex_if_integer, + } + ), + ), +) + TRANSACTION_REQUEST_FORMATTER = { "from": to_checksum_address, "to": apply_formatter_if(is_address, to_checksum_address), @@ -477,12 +524,13 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: "maxFeePerGas": to_hex_if_integer, "maxPriorityFeePerGas": to_hex_if_integer, "chainId": to_hex_if_integer, + "authorizationList": AUTH_LIST_REQUEST_FORMATTER, } transaction_request_formatter = type_aware_apply_formatters_to_dict( TRANSACTION_REQUEST_FORMATTER ) -ACCESS_LIST_REQUEST_FORMATTER = type_aware_apply_formatters_to_dict( +ETH_CALL_TX_FORMATTER = type_aware_apply_formatters_to_dict( { "accessList": apply_formatter_if( is_not_null, @@ -494,10 +542,11 @@ def storage_key_to_hexstr(value: Union[bytes, int, str]) -> HexStr: ) ), ), + "authorizationList": AUTH_LIST_REQUEST_FORMATTER, } ) transaction_param_formatter = compose( - ACCESS_LIST_REQUEST_FORMATTER, + ETH_CALL_TX_FORMATTER, remove_key_if("to", lambda txn: txn["to"] in {"", b"", None}), remove_key_if("gasPrice", lambda txn: txn["gasPrice"] in {"", b"", None}), ) @@ -1057,7 +1106,7 @@ def combine_formatters( def get_request_formatters( - method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]] + method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]], ) -> Dict[str, Callable[..., Any]]: request_formatter_maps = ( ABI_REQUEST_FORMATTERS, @@ -1083,7 +1132,7 @@ def raise_block_not_found(params: Tuple[BlockIdentifier, bool]) -> NoReturn: def raise_block_not_found_for_uncle_at_index( - params: Tuple[BlockIdentifier, Union[HexStr, int]] + params: Tuple[BlockIdentifier, Union[HexStr, int]], ) -> NoReturn: try: block_identifier = params[0] @@ -1109,7 +1158,7 @@ def raise_transaction_not_found(params: Tuple[_Hash32]) -> NoReturn: def raise_transaction_not_found_with_index( - params: Tuple[BlockIdentifier, int] + params: Tuple[BlockIdentifier, int], ) -> NoReturn: try: block_identifier = params[0] @@ -1213,7 +1262,7 @@ def get_result_formatters( def get_error_formatters( - method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]] + method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]], ) -> Callable[..., Any]: # Note error formatters work on the full response dict error_formatter_maps = (ERROR_FORMATTERS,) @@ -1223,7 +1272,7 @@ def get_error_formatters( def get_null_result_formatters( - method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]] + method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]], ) -> Callable[..., Any]: formatters = combine_formatters((NULL_RESULT_FORMATTERS,), method_name) diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index 8e7c2c7fb3..90e5f61b9f 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -57,7 +57,6 @@ from web3._utils.module_testing.module_testing_utils import ( assert_contains_log, async_mock_offchain_lookup_request_response, - flaky_geth_dev_mining, mock_offchain_lookup_request_response, ) from web3._utils.module_testing.utils import ( @@ -115,27 +114,6 @@ # "web3py" as an abi-encoded string WEB3PY_AS_HEXBYTES = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067765623370790000000000000000000000000000000000000000000000000000" # noqa: E501 -RLP_ACCESS_LIST = [ - ( - "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", - ( - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000007", - ), - ), - ("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", ()), -] - -RPC_ACCESS_LIST = [ - { - "address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", - "storageKeys": ( - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x0000000000000000000000000000000000000000000000000000000000000007", - ), - }, - {"address": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", "storageKeys": ()}, -] if TYPE_CHECKING: from _pytest.monkeypatch import MonkeyPatch # noqa: F401 @@ -638,9 +616,6 @@ async def test_eth_send_transaction_no_max_fee( assert txn["value"] == 1 assert txn["gas"] == 21000 - block = await async_w3.eth.get_block("latest") - assert txn["maxFeePerGas"] == maxPriorityFeePerGas + 2 * block["baseFeePerGas"] - @pytest.mark.asyncio async def test_eth_send_transaction_max_fee_less_than_tip( self, @@ -744,6 +719,86 @@ async def test_async_sign_and_send_raw_middleware( # clean up async_w3.middleware_onion.remove("signing") + @pytest.mark.asyncio + async def test_async_sign_authorization_and_send_raw_set_code_transaction( + self, + async_w3: "AsyncWeb3", + keyfile_account_pkey: HexStr, + async_math_contract: "AsyncContract", + ) -> None: + keyfile_account = async_w3.eth.account.from_key(keyfile_account_pkey) + + chain_id = await async_w3.eth.chain_id + nonce = await async_w3.eth.get_transaction_count(keyfile_account.address) + + auth = { + "chainId": chain_id, + "address": async_math_contract.address, + "nonce": nonce + 1, + } + signed_auth = keyfile_account.sign_authorization(auth) + + # get current math counter and increase it only in the delegation by n + math_counter = await async_math_contract.functions.counter().call() + built_tx = await async_math_contract.functions.incrementCounter( + math_counter + 1337 + ).build_transaction({}) + txn: TxParams = { + "chainId": chain_id, + "to": keyfile_account.address, + "value": Wei(0), + "gas": 200_000, + "nonce": nonce, + "maxPriorityFeePerGas": Wei(10**9), + "maxFeePerGas": Wei(10**9), + "data": built_tx["data"], + "authorizationList": [signed_auth], + } + + signed = keyfile_account.sign_transaction(txn) + tx_hash = await async_w3.eth.send_raw_transaction(signed.raw_transaction) + get_tx = await async_w3.eth.get_transaction(tx_hash) + await async_w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10) + + code = await async_w3.eth.get_code(keyfile_account.address) + assert code.to_0x_hex() == f"0xef0100{async_math_contract.address[2:].lower()}" + delegated = async_w3.eth.contract( + address=keyfile_account.address, abi=async_math_contract.abi + ) + # assert the math counter is increased by 1337 only in delegated acct + assert await async_math_contract.functions.counter().call() == math_counter + delegated_call = await delegated.functions.counter().call() + assert delegated_call == math_counter + 1337 + + assert len(get_tx["authorizationList"]) == 1 + get_auth = get_tx["authorizationList"][0] + assert get_auth["chainId"] == chain_id + assert get_auth["address"] == async_math_contract.address + assert get_auth["nonce"] == nonce + 1 + assert isinstance(get_auth["yParity"], int) + assert isinstance(get_auth["r"], HexBytes) + assert isinstance(get_auth["s"], HexBytes) + + # reset code + reset_auth = { + "chainId": chain_id, + "address": "0x" + ("00" * 20), + "nonce": nonce + 3, + } + signed_reset_auth = keyfile_account.sign_authorization(reset_auth) + new_txn = dict(txn) + new_txn["authorizationList"] = [signed_reset_auth] + new_txn["nonce"] = nonce + 2 + + signed_reset = keyfile_account.sign_transaction(new_txn) + reset_tx_hash = await async_w3.eth.send_raw_transaction( + signed_reset.raw_transaction + ) + await async_w3.eth.wait_for_transaction_receipt(reset_tx_hash, timeout=10) + + reset_code = await async_w3.eth.get_code(keyfile_account.address) + assert reset_code == HexBytes("0x") + @pytest.mark.asyncio async def test_GasPriceStrategyMiddleware( self, @@ -1763,7 +1818,6 @@ async def test_async_eth_get_transaction_receipt_mined( assert isinstance(effective_gas_price, int) assert effective_gas_price > 0 - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_get_transaction_receipt_unmined( self, @@ -1829,7 +1883,6 @@ async def test_async_eth_wait_for_transaction_receipt_mined( assert isinstance(effective_gas_price, int) assert effective_gas_price > 0 - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_wait_for_transaction_receipt_unmined( self, @@ -2255,7 +2308,6 @@ async def test_async_eth_sign_ens_names( assert is_bytes(signature) assert len(signature) == 32 + 32 + 1 - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_legacy( self, @@ -2285,7 +2337,6 @@ async def test_async_eth_replace_transaction_legacy( assert replace_txn["gas"] == 21000 assert replace_txn["gasPrice"] == txn_params["gasPrice"] - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction( self, @@ -2322,7 +2373,6 @@ async def test_async_eth_replace_transaction( assert replace_txn["maxFeePerGas"] == three_gwei_in_wei assert replace_txn["maxPriorityFeePerGas"] == two_gwei_in_wei - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_underpriced( self, @@ -2349,7 +2399,6 @@ async def test_async_eth_replace_transaction_underpriced( with pytest.raises(Web3RPCError, match="replacement transaction underpriced"): await async_w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_non_existing_transaction( self, @@ -2372,7 +2421,6 @@ async def test_async_eth_replace_transaction_non_existing_transaction( txn_params, ) - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_already_mined( self, @@ -2395,7 +2443,6 @@ async def test_async_eth_replace_transaction_already_mined( with pytest.raises(Web3ValueError, match="Supplied transaction with hash"): await async_w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_incorrect_nonce( self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress @@ -2417,7 +2464,6 @@ async def test_async_eth_replace_transaction_incorrect_nonce( with pytest.raises(Web3ValueError): await async_w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_gas_price_too_low( self, @@ -2437,7 +2483,6 @@ async def test_async_eth_replace_transaction_gas_price_too_low( with pytest.raises(Web3ValueError): await async_w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_gas_price_defaulting_minimum( self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress @@ -2461,7 +2506,6 @@ async def test_async_eth_replace_transaction_gas_price_defaulting_minimum( gas_price * 1.125 ) # minimum gas price - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_higher( self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress @@ -2490,7 +2534,6 @@ def higher_gas_price_strategy(_async_w3: "AsyncWeb3", _txn: TxParams) -> Wei: ) # Strategy provides higher gas price async_w3.eth.set_gas_price_strategy(None) # reset strategy - @flaky_geth_dev_mining @pytest.mark.asyncio async def test_async_eth_replace_transaction_gas_price_defaulting_strategy_lower( self, async_w3: "AsyncWeb3", async_keyfile_account_address: ChecksumAddress @@ -3464,7 +3507,6 @@ def gas_price_strategy(_w3: "Web3", _txn: TxParams) -> str: assert txn["gasPrice"] == two_gwei_in_wei w3.eth.set_gas_price_strategy(None) # reset strategy - @flaky_geth_dev_mining def test_eth_replace_transaction_legacy( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3492,8 +3534,8 @@ def test_eth_replace_transaction_legacy( assert replace_txn["value"] == 1 assert replace_txn["gas"] == 21000 assert replace_txn["gasPrice"] == txn_params["gasPrice"] + print("yeeeeeeehhhboiiiiiiiii") - @flaky_geth_dev_mining def test_eth_replace_transaction( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3527,7 +3569,6 @@ def test_eth_replace_transaction( assert replace_txn["maxFeePerGas"] == three_gwei_in_wei assert replace_txn["maxPriorityFeePerGas"] == two_gwei_in_wei - @flaky_geth_dev_mining def test_eth_replace_transaction_underpriced( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3551,7 +3592,6 @@ def test_eth_replace_transaction_underpriced( with pytest.raises(Web3RPCError, match="replacement transaction underpriced"): w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining def test_eth_replace_transaction_non_existing_transaction( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3571,7 +3611,6 @@ def test_eth_replace_transaction_non_existing_transaction( txn_params, ) - @flaky_geth_dev_mining def test_eth_replace_transaction_already_mined( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3591,7 +3630,6 @@ def test_eth_replace_transaction_already_mined( with pytest.raises(Web3ValueError, match="Supplied transaction with hash"): w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining def test_eth_replace_transaction_incorrect_nonce( self, w3: "Web3", keyfile_account_address: ChecksumAddress ) -> None: @@ -3612,7 +3650,6 @@ def test_eth_replace_transaction_incorrect_nonce( with pytest.raises(Web3ValueError): w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining def test_eth_replace_transaction_gas_price_too_low( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -3629,7 +3666,6 @@ def test_eth_replace_transaction_gas_price_too_low( with pytest.raises(Web3ValueError): w3.eth.replace_transaction(txn_hash, txn_params) - @flaky_geth_dev_mining def test_eth_replace_transaction_gas_price_defaulting_minimum( self, w3: "Web3", keyfile_account_address: ChecksumAddress ) -> None: @@ -3652,7 +3688,6 @@ def test_eth_replace_transaction_gas_price_defaulting_minimum( gas_price * 1.125 ) # minimum gas price - @flaky_geth_dev_mining def test_eth_replace_transaction_gas_price_defaulting_strategy_higher( self, w3: "Web3", keyfile_account_address: ChecksumAddress ) -> None: @@ -3680,7 +3715,6 @@ def higher_gas_price_strategy(w3: "Web3", txn: TxParams) -> Wei: ) # Strategy provides higher gas price w3.eth.set_gas_price_strategy(None) # reset strategy - @flaky_geth_dev_mining def test_eth_replace_transaction_gas_price_defaulting_strategy_lower( self, w3: "Web3", keyfile_account_address: ChecksumAddress ) -> None: @@ -3806,6 +3840,79 @@ def test_sign_and_send_raw_middleware( # cleanup w3.middleware_onion.remove("signing") + def test_sign_authorization_and_send_raw_set_code_transaction( + self, w3: "Web3", keyfile_account_pkey: HexStr, math_contract: "Contract" + ) -> None: + keyfile_account = w3.eth.account.from_key(keyfile_account_pkey) + + chain_id = w3.eth.chain_id + nonce = w3.eth.get_transaction_count(keyfile_account.address) + + auth = { + "chainId": chain_id, + "address": math_contract.address, + "nonce": nonce + 1, + } + signed_auth = keyfile_account.sign_authorization(auth) + + # get current math counter and increase it only in the delegation by n + math_counter = math_contract.functions.counter().call() + data = math_contract.functions.incrementCounter( + math_counter + 1337 + ).build_transaction({})["data"] + txn: TxParams = { + "chainId": chain_id, + "to": keyfile_account.address, + "value": Wei(0), + "gas": 200_000, + "nonce": nonce, + "maxPriorityFeePerGas": Wei(10**9), + "maxFeePerGas": Wei(10**9), + "data": data, + "authorizationList": [signed_auth], + } + + signed = keyfile_account.sign_transaction(txn) + tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction) + get_tx = w3.eth.get_transaction(tx_hash) + w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10) + + code = w3.eth.get_code(keyfile_account.address) + assert code.to_0x_hex() == f"0xef0100{math_contract.address[2:].lower()}" + delegated = w3.eth.contract( + address=keyfile_account.address, abi=math_contract.abi + ) + # assert the math counter is increased by 1337 only in delegated acct + assert math_contract.functions.counter().call() == math_counter + assert delegated.functions.counter().call() == math_counter + 1337 + + assert len(get_tx["authorizationList"]) == 1 + get_auth = get_tx["authorizationList"][0] + assert get_auth["chainId"] == chain_id + assert get_auth["address"] == math_contract.address + assert get_auth["nonce"] == nonce + 1 + assert isinstance(get_auth["yParity"], int) + assert isinstance(get_auth["r"], HexBytes) + assert isinstance(get_auth["s"], HexBytes) + + # reset storage value and code + reset_auth = { + "chainId": chain_id, + "address": "0x" + ("00" * 20), + "nonce": nonce + 3, + } + signed_reset_auth = keyfile_account.sign_authorization(reset_auth) + new_txn = dict(txn) + new_txn["authorizationList"] = [signed_reset_auth] + new_txn["nonce"] = nonce + 2 + + signed_reset = keyfile_account.sign_transaction(new_txn) + reset_tx_hash = w3.eth.send_raw_transaction(signed_reset.raw_transaction) + w3.eth.wait_for_transaction_receipt(reset_tx_hash, timeout=10) + + reset_code = w3.eth.get_code(keyfile_account.address) + assert reset_code == HexBytes("0x") + def test_eth_call(self, w3: "Web3", math_contract: "Contract") -> None: txn_params = math_contract._prepare_transaction( abi_element_identifier="add", @@ -4514,7 +4621,6 @@ def test_eth_get_transaction_receipt_mined( assert isinstance(effective_gas_price, int) assert effective_gas_price > 0 - @flaky_geth_dev_mining def test_eth_get_transaction_receipt_unmined( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: @@ -4572,7 +4678,6 @@ def test_eth_wait_for_transaction_receipt_mined( assert isinstance(effective_gas_price, int) assert effective_gas_price > 0 - @flaky_geth_dev_mining def test_eth_wait_for_transaction_receipt_unmined( self, w3: "Web3", keyfile_account_address_dual_type: ChecksumAddress ) -> None: diff --git a/web3/_utils/module_testing/module_testing_utils.py b/web3/_utils/module_testing/module_testing_utils.py index 289b3386b1..f369d07804 100644 --- a/web3/_utils/module_testing/module_testing_utils.py +++ b/web3/_utils/module_testing/module_testing_utils.py @@ -21,9 +21,6 @@ from eth_utils import ( is_same_address, ) -from flaky import ( - flaky, -) from hexbytes import ( HexBytes, ) @@ -44,20 +41,11 @@ ) from requests import Response # noqa: F401 - from web3 import Web3 # noqa: F401 from web3._utils.compat import ( # noqa: F401 Self, ) -""" -flaky_geth_dev_mining decorator for tests requiring a pending block -for the duration of the test. This behavior can be flaky -due to timing of the test running as a block is mined. -""" -flaky_geth_dev_mining = flaky(max_runs=3, min_passes=1) - - def assert_contains_log( result: Sequence[LogReceipt], block_with_txn_with_log: BlockData, diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py index 72ab204076..6a22a083b6 100644 --- a/web3/providers/eth_tester/middleware.py +++ b/web3/providers/eth_tester/middleware.py @@ -78,6 +78,7 @@ def is_hexstr(value: Any) -> bool: "maxFeePerGas": "max_fee_per_gas", "maxPriorityFeePerGas": "max_priority_fee_per_gas", "accessList": "access_list", + "authorizationList": "authorization_list", "chainId": "chain_id", } transaction_request_remapper = apply_key_map(TRANSACTION_REQUEST_KEY_MAPPING) @@ -94,6 +95,20 @@ def is_hexstr(value: Any) -> bool: "accessList": apply_list_to_array_formatter( apply_key_map({"storageKeys": "storage_keys"}) ), + "authorizationList": apply_list_to_array_formatter( + compose( + apply_formatters_to_dict( + { + "chain_id": to_integer_if_hex, + "nonce": to_integer_if_hex, + "y_parity": to_integer_if_hex, + "r": to_integer_if_hex, + "s": to_integer_if_hex, + }, + ), + apply_key_map({"chainId": "chain_id", "yParity": "y_parity"}), + ) + ), } transaction_request_formatter = apply_formatters_to_dict(TRANSACTION_REQUEST_FORMATTERS) @@ -125,6 +140,7 @@ def is_hexstr(value: Any) -> bool: TRANSACTION_RESULT_KEY_MAPPING = { "access_list": "accessList", + "authorization_list": "authorizationList", "blob_versioned_hashes": "blobVersionedHashes", "block_hash": "blockHash", "block_number": "blockNumber", @@ -145,6 +161,9 @@ def is_hexstr(value: Any) -> bool: "access_list": apply_list_to_array_formatter( apply_key_map({"storage_keys": "storageKeys"}), ), + "authorization_list": apply_list_to_array_formatter( + apply_key_map({"chain_id": "chainId", "y_parity": "yParity"}), + ), } transaction_result_formatter = apply_formatters_to_dict(TRANSACTION_RESULT_FORMATTERS) @@ -195,6 +214,7 @@ def is_hexstr(value: Any) -> bool: "parent_beacon_block_root": "parentBeaconBlockRoot", "blob_gas_used": "blobGasUsed", "excess_blob_gas": "excessBlobGas", + "requests_hash": "requestsHash", } block_result_remapper = apply_key_map(BLOCK_RESULT_KEY_MAPPING) diff --git a/web3/types.py b/web3/types.py index 240e859695..8a1f67ad44 100644 --- a/web3/types.py +++ b/web3/types.py @@ -16,6 +16,9 @@ Union, ) +from eth_account.datastructures import ( + SignedSetCodeAuthorization, +) from eth_typing import ( Address, BlockNumber, @@ -94,11 +97,21 @@ class RPCError(TypedDict): data: NotRequired[str] +class SetCodeAuthorizationData(TypedDict): + chainId: int + address: ChecksumAddress + nonce: Nonce + yParity: int + r: HexBytes + s: HexBytes + + # syntax b/c "from" keyword not allowed w/ class construction TxData = TypedDict( "TxData", { "accessList": AccessList, + "authorizationList": Sequence[SetCodeAuthorizationData], "blobVersionedHashes": Sequence[HexBytes], "blockHash": HexBytes, "blockNumber": BlockNumber, @@ -125,11 +138,24 @@ class RPCError(TypedDict): total=False, ) + +class SetCodeAuthorizationParams(TypedDict): + chainId: int + address: Union[Address, ChecksumAddress, str] + nonce: Nonce + y_parity: int + r: int + s: int + + # syntax b/c "from" keyword not allowed w/ class construction TxParams = TypedDict( "TxParams", { "accessList": AccessList, + "authorizationList": Sequence[ + Union[SetCodeAuthorizationParams, SignedSetCodeAuthorization] + ], "blobVersionedHashes": Sequence[Union[str, HexStr, bytes, HexBytes]], "chainId": int, "data": Union[bytes, HexStr], @@ -186,6 +212,7 @@ class BlockData(TypedDict, total=False): parentBeaconBlockRoot: HexBytes blobGasUsed: int excessBlobGas: int + requestsHash: HexBytes # ExtraDataToPOAMiddleware replaces extraData w/ proofOfAuthorityData proofOfAuthorityData: HexBytes