From 4440083663e25309b5d6288da2a3dcd260837c2f Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 11:30:01 +0000 Subject: [PATCH 01/11] add async multi provider classes --- .../multi_http_provider_async.py | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 web3_multi_provider/multi_http_provider_async.py diff --git a/web3_multi_provider/multi_http_provider_async.py b/web3_multi_provider/multi_http_provider_async.py new file mode 100644 index 0000000..7d06380 --- /dev/null +++ b/web3_multi_provider/multi_http_provider_async.py @@ -0,0 +1,152 @@ +import logging +from abc import ABC +from typing import Any + +from eth_typing import URI +from web3 import AsyncHTTPProvider +from web3._utils.empty import Empty, empty +from web3._utils.rpc_abi import RPC +from web3.exceptions import ExtraDataLengthError +from web3.middleware.proof_of_authority import extradata_to_poa_cleanup +from web3.middleware.validation import _check_extradata_length +from web3.providers.async_base import AsyncJSONBaseProvider +from web3.providers.rpc.utils import ExceptionRetryConfiguration +from web3.types import RPCEndpoint, RPCResponse + +logger = logging.getLogger(__name__) + + +class NoActiveProviderError(Exception): + """Base exception if all providers are offline""" + + +class ProtocolNotSupported(Exception): + """Supported protocols: http, https""" + + +class AsyncBaseMultiProvider(AsyncJSONBaseProvider, ABC): + """Base async provider for providers with multiple endpoints""" + + _providers: list[AsyncHTTPProvider] + + def __init__( # pylint: disable=too-many-arguments + self, + endpoint_urls: list[URI | str], + request_kwargs: Any | None = None, + exception_retry_configuration: ( + ExceptionRetryConfiguration | Empty | None + ) = empty, + **kwargs: Any, + ): + logger.debug({"msg": f"Initialize {self.__class__.__name__}"}) + self._hosts_uri = endpoint_urls + self._providers = [] + + if endpoint_urls: + self.endpoint_uri = endpoint_urls[0] + + for endpoint_uri in endpoint_urls: + if not endpoint_uri.startswith("http"): + protocol = endpoint_uri.split("://")[0] + raise ProtocolNotSupported(f'Protocol "{protocol}" is not supported.') + + self._providers.append( + AsyncHTTPProvider( + endpoint_uri=endpoint_uri, + request_kwargs=request_kwargs, + exception_retry_configuration=exception_retry_configuration, + **kwargs, + ) + ) + + super().__init__() + + @staticmethod + def _sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: + if method in (RPC.eth_getBlockByHash, RPC.eth_getBlockByNumber): + if ( + "result" in response + and isinstance(response["result"], dict) + and "extraData" in response["result"] + and "proofOfAuthorityData" not in response["result"] + ): + try: + _check_extradata_length(response["result"]["extraData"]) + except ExtraDataLengthError: + logger.debug({"msg": "PoA blockchain cleanup response."}) + response["result"] = extradata_to_poa_cleanup(response["result"]) + + +class AsyncMultiProvider(AsyncBaseMultiProvider): + """ + Provider that switches rpc endpoint to next if current is broken. + """ + + _current_provider_index: int = 0 + + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + providers_count = len(self._providers) + + for _ in range(providers_count): + active_provider = self._providers[self._current_provider_index] + + try: + response = await active_provider.make_request(method, params) + except Exception as error: # pylint: disable=broad-except + self._current_provider_index = ( + self._current_provider_index + 1 + ) % providers_count + logger.warning( + { + "msg": "Provider not responding.", + "error": str(error).replace( + str(active_provider.endpoint_uri), "****" + ), + } + ) + else: + self._sanitize_poa_response(method, response) + + logger.debug( + { + "msg": "Send request using MultiProvider.", + "method": method, + "params": str(params), + } + ) + return response + + msg = "No active provider available." + logger.debug({"msg": msg}) + raise NoActiveProviderError(msg) + + +class AsyncFallbackProvider(AsyncBaseMultiProvider): + """Basic fallback provider""" + + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + for provider in self._providers: + try: + response = await provider.make_request(method, params) + except Exception as error: # pylint: disable=broad-except + logger.warning( + { + "msg": "Provider not responding.", + "error": str(error).replace(str(provider.endpoint_uri), "****"), + } + ) + else: + self._sanitize_poa_response(method, response) + + logger.debug( + { + "msg": "Send request using FallbackProvider.", + "method": method, + "params": str(params), + } + ) + return response + + msg = "No active provider available." + logger.debug({"msg": msg}) + raise NoActiveProviderError(msg) From 46c9019aaabcbf506097c24fe0f5abe254043356 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 13:01:56 +0000 Subject: [PATCH 02/11] factor out common poa code and exceptions --- web3_multi_provider/exceptions.py | 8 +++++ web3_multi_provider/multi_http_provider.py | 34 +++---------------- .../multi_http_provider_async.py | 33 +++--------------- web3_multi_provider/poa.py | 24 +++++++++++++ 4 files changed, 42 insertions(+), 57 deletions(-) create mode 100644 web3_multi_provider/exceptions.py create mode 100644 web3_multi_provider/poa.py diff --git a/web3_multi_provider/exceptions.py b/web3_multi_provider/exceptions.py new file mode 100644 index 0000000..b1fee77 --- /dev/null +++ b/web3_multi_provider/exceptions.py @@ -0,0 +1,8 @@ +class NoActiveProviderError(Exception): + """Base exception if all providers are offline""" + + +class ProtocolNotSupported(Exception): + """Supported protocols: http, https""" + + diff --git a/web3_multi_provider/multi_http_provider.py b/web3_multi_provider/multi_http_provider.py index d439abf..91cff76 100644 --- a/web3_multi_provider/multi_http_provider.py +++ b/web3_multi_provider/multi_http_provider.py @@ -5,23 +5,14 @@ from eth_typing import URI from web3 import HTTPProvider from web3._utils.empty import Empty, empty -from web3._utils.rpc_abi import RPC -from web3.exceptions import ExtraDataLengthError -from web3.middleware.proof_of_authority import extradata_to_poa_cleanup -from web3.middleware.validation import _check_extradata_length from web3.providers import JSONBaseProvider from web3.providers.rpc.utils import ExceptionRetryConfiguration from web3.types import RPCEndpoint, RPCResponse -logger = logging.getLogger(__name__) - - -class NoActiveProviderError(Exception): - """Base exception if all providers are offline""" +from web3_multi_provider.poa import sanitize_poa_response +from web3_multi_provider.exceptions import NoActiveProviderError, ProtocolNotSupported - -class ProtocolNotSupported(Exception): - """Supported protocols: http, https""" +logger = logging.getLogger(__name__) class BaseMultiProvider(JSONBaseProvider, ABC): @@ -63,21 +54,6 @@ def __init__( # pylint: disable=too-many-arguments super().__init__() - @staticmethod - def _sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: - if method in (RPC.eth_getBlockByHash, RPC.eth_getBlockByNumber): - if ( - "result" in response - and isinstance(response["result"], dict) - and "extraData" in response["result"] - and "proofOfAuthorityData" not in response["result"] - ): - try: - _check_extradata_length(response["result"]["extraData"]) - except ExtraDataLengthError: - logger.debug({"msg": "PoA blockchain cleanup response."}) - response["result"] = extradata_to_poa_cleanup(response["result"]) - class MultiProvider(BaseMultiProvider): """ @@ -107,7 +83,7 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: } ) else: - self._sanitize_poa_response(method, response) + sanitize_poa_response(method, response) logger.debug( { @@ -138,7 +114,7 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: } ) else: - self._sanitize_poa_response(method, response) + sanitize_poa_response(method, response) logger.debug( { diff --git a/web3_multi_provider/multi_http_provider_async.py b/web3_multi_provider/multi_http_provider_async.py index 7d06380..1911359 100644 --- a/web3_multi_provider/multi_http_provider_async.py +++ b/web3_multi_provider/multi_http_provider_async.py @@ -5,23 +5,15 @@ from eth_typing import URI from web3 import AsyncHTTPProvider from web3._utils.empty import Empty, empty -from web3._utils.rpc_abi import RPC -from web3.exceptions import ExtraDataLengthError -from web3.middleware.proof_of_authority import extradata_to_poa_cleanup -from web3.middleware.validation import _check_extradata_length from web3.providers.async_base import AsyncJSONBaseProvider from web3.providers.rpc.utils import ExceptionRetryConfiguration from web3.types import RPCEndpoint, RPCResponse -logger = logging.getLogger(__name__) - - -class NoActiveProviderError(Exception): - """Base exception if all providers are offline""" +from web3_multi_provider.poa import sanitize_poa_response +from web3_multi_provider.exceptions import NoActiveProviderError, ProtocolNotSupported +logger = logging.getLogger(__name__) -class ProtocolNotSupported(Exception): - """Supported protocols: http, https""" class AsyncBaseMultiProvider(AsyncJSONBaseProvider, ABC): @@ -61,21 +53,6 @@ def __init__( # pylint: disable=too-many-arguments super().__init__() - @staticmethod - def _sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: - if method in (RPC.eth_getBlockByHash, RPC.eth_getBlockByNumber): - if ( - "result" in response - and isinstance(response["result"], dict) - and "extraData" in response["result"] - and "proofOfAuthorityData" not in response["result"] - ): - try: - _check_extradata_length(response["result"]["extraData"]) - except ExtraDataLengthError: - logger.debug({"msg": "PoA blockchain cleanup response."}) - response["result"] = extradata_to_poa_cleanup(response["result"]) - class AsyncMultiProvider(AsyncBaseMultiProvider): """ @@ -105,7 +82,7 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: } ) else: - self._sanitize_poa_response(method, response) + sanitize_poa_response(method, response) logger.debug( { @@ -136,7 +113,7 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: } ) else: - self._sanitize_poa_response(method, response) + sanitize_poa_response(method, response) logger.debug( { diff --git a/web3_multi_provider/poa.py b/web3_multi_provider/poa.py new file mode 100644 index 0000000..be21e88 --- /dev/null +++ b/web3_multi_provider/poa.py @@ -0,0 +1,24 @@ +import logging + +from web3.types import RPCEndpoint, RPCResponse +from web3._utils.rpc_abi import RPC +from web3.middleware.validation import _check_extradata_length +from web3.exceptions import ExtraDataLengthError +from web3.middleware.proof_of_authority import extradata_to_poa_cleanup + +logger = logging.getLogger(__name__) + +def sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: + if method in (RPC.eth_getBlockByHash, RPC.eth_getBlockByNumber): + if ( + "result" in response + and isinstance(response["result"], dict) + and "extraData" in response["result"] + and "proofOfAuthorityData" not in response["result"] + ): + try: + _check_extradata_length(response["result"]["extraData"]) + except ExtraDataLengthError: + logger.debug({"msg": "PoA blockchain cleanup response."}) + response["result"] = extradata_to_poa_cleanup(response["result"]) + From 44e69d9952bdf3e21a44aae74a5ce4a29c7debf7 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 13:04:32 +0000 Subject: [PATCH 03/11] rename and add imports to __init__.py --- web3_multi_provider/__init__.py | 10 ++++++++++ ..._provider_async.py => async_multi_http_provider.py} | 0 2 files changed, 10 insertions(+) rename web3_multi_provider/{multi_http_provider_async.py => async_multi_http_provider.py} (100%) diff --git a/web3_multi_provider/__init__.py b/web3_multi_provider/__init__.py index 5d68a53..f55045f 100644 --- a/web3_multi_provider/__init__.py +++ b/web3_multi_provider/__init__.py @@ -1,6 +1,14 @@ from .multi_http_provider import ( FallbackProvider, MultiProvider, +) + +from .async_multi_http_provider import ( + AsyncFallbackProvider, + AsyncMultiProvider, +) + +from .exceptions import ( NoActiveProviderError, ProtocolNotSupported, ) @@ -8,6 +16,8 @@ __all__ = ( "FallbackProvider", "MultiProvider", + "AsyncFallbackProvider", + "AsyncMultiProvider", "NoActiveProviderError", "ProtocolNotSupported", ) diff --git a/web3_multi_provider/multi_http_provider_async.py b/web3_multi_provider/async_multi_http_provider.py similarity index 100% rename from web3_multi_provider/multi_http_provider_async.py rename to web3_multi_provider/async_multi_http_provider.py From 32e261ce1b307a9c4c36fd32088a6dd9edcb620c Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 13:18:43 +0000 Subject: [PATCH 04/11] add unit tests for async HTTP providers --- poetry.lock | 22 ++- pyproject.toml | 1 + tests/test_async_http_provider.py | 248 ++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 tests/test_async_http_provider.py diff --git a/poetry.lock b/poetry.lock index 3c99daa..e2497d4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1780,6 +1780,24 @@ pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "pytest-cov" version = "5.0.0" @@ -2370,4 +2388,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = ">=3.12,<4" -content-hash = "f10d385ea80f64c6e9f8b3302f28c03bfd28a9cf29abac14e2bb03ded4bab85a" +content-hash = "a0ff94d0c181315b7473217c21c1e2bd90903c4e0110e2026fdc0938ddba4b15" diff --git a/pyproject.toml b/pyproject.toml index 72e3df8..81f2a6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ include = [ [tool.poetry.dependencies] python = ">=3.12,<4" web3 = ">=7,<8" +pytest-asyncio = "^0.24.0" [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" diff --git a/tests/test_async_http_provider.py b/tests/test_async_http_provider.py new file mode 100644 index 0000000..69641d4 --- /dev/null +++ b/tests/test_async_http_provider.py @@ -0,0 +1,248 @@ +import logging +from unittest import TestCase +from unittest.mock import Mock, patch + +import pytest +from web3 import AsyncWeb3 + +from web3_multi_provider import ( + AsyncMultiProvider, + AsyncFallbackProvider, + NoActiveProviderError, + ProtocolNotSupported, +) + + +async def mocked_requests_get( + endpoint_uri, + data, + *args, + **kwargs, +): + if "http://127.0.0.1:9000" in endpoint_uri: + return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x486976656f6e2065752d68656176792d32", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' + + raise ConnectionError("Mocked connection error.") + + +async def mocked_request_poa( + endpoint_uri, + data, + *args, + **kwargs, +): + return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x00000000000000000000000051396620476f65726c6920417574686f72697479a8766f851c7ae7b3be68b8766225f28c8a0daf86bcdcdc7cb6a2cadec54bd393506e7d2088192110067a6d6280b13a2430d6b44dd2dbbe93d190ddce4309b83500", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' + + +class HttpProviderTestCase(TestCase): + @pytest.fixture(autouse=True) + def __inject_fixtures(self, caplog): + self._caplog = caplog + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_one_provider_works(self, make_post_request): + await self.one_provider_works(AsyncMultiProvider) + await self.one_provider_works(AsyncFallbackProvider) + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_nothing_works(self, make_post_request): + self._caplog.set_level(logging.WARNING) + + provider = AsyncMultiProvider( + [ + "http://127.0.0.1:9001", + "http://127.0.0.1:9002", + ] + ) + + w3 = AsyncWeb3(provider) + + with self._caplog.at_level(logging.DEBUG): + with self.assertRaises(NoActiveProviderError): + await w3.eth.get_block("latest") + + # Make sure there is no inf recursion + self.assertEqual(len(self._caplog.records), 6) + + async def one_provider_works(self, provider_class): + provider = provider_class( + [ + "http://127.0.0.1:9001", + "http://127.0.0.1:9000", + ] + ) + + w3 = AsyncWeb3(provider) + + with self._caplog.at_level(logging.DEBUG): + await w3.eth.get_block("latest") + await w3.eth.get_block("latest") + + self.assertDictEqual( + { + "msg": "Provider not responding.", + "error": "Mocked connection error.", + }, + self._caplog.records[2].msg, + ) + self.assertDictEqual( + { + "msg": "Send request using AsyncMultiProvider.", + "method": "eth_getBlockByNumber", + "params": "('latest', False)", + }, + self._caplog.records[5].msg, + ) + # Make sure second request will be directory to second provider and will ignore second one + self.assertDictEqual( + { + "msg": "Send request using AsyncMultiProvider.", + "method": "eth_getBlockByNumber", + "params": "('latest', False)", + }, + self._caplog.records[9].msg, + ) + + def test_protocols_support(self): + AsyncMultiProvider(["http://127.0.0.1:9001"]) + AsyncMultiProvider(["https://127.0.0.1:9001"]) + + with self.assertRaises(ProtocolNotSupported): + AsyncMultiProvider(["ipc://127.0.0.1:9001"]) + + with self.assertRaises(ProtocolNotSupported): + AsyncMultiProvider(["ws://127.0.0.1:9001"]) + + with self.assertRaises(ProtocolNotSupported): + AsyncMultiProvider(["wss://127.0.0.1:9001"]) + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_request_poa) + async def test_poa_blockchain(self, make_post_request): + provider = AsyncMultiProvider(["http://127.0.0.1:9000"]) + + w3 = AsyncWeb3(provider) + + with self._caplog.at_level(logging.DEBUG): + block = await w3.eth.get_block("latest") + + self.assertIn( + {"msg": "PoA blockchain cleanup response."}, + [log.msg for log in self._caplog.records], + ) + + self.assertIsNotNone(block.get("proofOfAuthorityData", None)) + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_pos_blockchain(self, make_post_request): + provider = AsyncMultiProvider(["http://127.0.0.1:9000"]) + + w3 = AsyncWeb3(provider) + + with self._caplog.at_level(logging.DEBUG): + block = await w3.eth.get_block("latest") + + self.assertIsNone(block.get("proofOfAuthorityData", None)) + + self.assertNotIn( + {"msg": "PoA blockchain cleanup response."}, + [log.msg for log in self._caplog.records], + ) + + +class TestAsyncFallbackProvider: + @pytest.mark.asyncio + async def test_no_endpoints(self): + w3 = AsyncWeb3(AsyncFallbackProvider([])) + + with pytest.raises(NoActiveProviderError): + await w3.eth.get_block("latest") + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_one_endpoint(self, make_post_request: Mock): + w3 = AsyncWeb3( + AsyncFallbackProvider( + [ + "http://127.0.0.1:9000", + ], + exception_retry_configuration=None, + ) + ) + await w3.eth.get_block("latest") + make_post_request.assert_called_once() + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_first_working(self, make_post_request: Mock): + w3 = AsyncWeb3( + AsyncFallbackProvider( + [ + "http://127.0.0.1:9000", + "http://127.0.0.1:9001", + ], + exception_retry_configuration=None, + ) + ) + await w3.eth.get_block("latest") + make_post_request.assert_called_once() + assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_all_endpoints_fail(self, make_post_request: Mock): + w3 = AsyncWeb3( + AsyncFallbackProvider( + [ + "http://127.0.0.1:9001", + "http://127.0.0.1:9002", + "http://127.0.0.1:9003", + ], + exception_retry_configuration=None, + ) + ) + + with pytest.raises(NoActiveProviderError): + await w3.eth.get_block("latest") + + assert make_post_request.call_count == 3 + assert make_post_request.call_args.args[0] == "http://127.0.0.1:9003" + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_one_endpoint_works(self, make_post_request: Mock): + w3 = AsyncWeb3( + AsyncFallbackProvider( + [ + "http://127.0.0.1:9001", + "http://127.0.0.1:9000", + ], + exception_retry_configuration=None, + ) + ) + + await w3.eth.get_block("latest") + assert make_post_request.call_count == 2 + assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" + + @pytest.mark.asyncio + @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) + async def test_starts_from_the_first(self, make_post_request: Mock): + w3 = AsyncWeb3( + AsyncFallbackProvider( + [ + "http://127.0.0.1:9001", + "http://127.0.0.1:9000", + ], + exception_retry_configuration=None, + ) + ) + + await w3.eth.get_block("latest") + await w3.eth.get_block("latest") + + assert make_post_request.call_count == 4 + assert make_post_request.call_args_list[-2].args[0] == "http://127.0.0.1:9001" From a788a91ef36e4798dab7b952665c344695463dca Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 14:26:20 +0000 Subject: [PATCH 05/11] update README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07e7def..2babbdf 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,20 @@ last_block = w3.eth.get_block('latest') ### `MultiProvider` -This provider keeps track of the current endpoint and switches to the next one if an error occurs. +This provider keeps track of the current endpoint and switches to the next one if an error occurs. It fails if no endpoints are available. ### `FallbackProvider` This provider sends requests to the all endpoints in the sequence until response received or endpoints list exhausted. +### `AsyncMultiProvider` and `AsyncFallbackProvider` + +These providers are async versions of `MultiProvider` and `FallbackProvider` respectively. They may +be used with instances of `AsyncWeb3`. + +```py + ## For developers 1. `poetry install` - to install deps From 5955bf15096ed7e6426f3e95f1025f8d9594bce9 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 14:29:12 +0000 Subject: [PATCH 06/11] add usage example to README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 2babbdf..b2bb246 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,15 @@ These providers are async versions of `MultiProvider` and `FallbackProvider` res be used with instances of `AsyncWeb3`. ```py +from web3 import AsyncWeb3 +from web3_multi_provider import AsyncMultiProvider +from web3_multi_provider import AsyncFallbackProvider + +w3 = AsyncWeb3(AsyncMultiProvider([ # RPC endpoints list + 'http://127.0.0.1:8000/', + 'https://mainnet.infura.io/v3/...', +])) +``` ## For developers From d1a263b3ed018d0e5f0edcd07309828ef71747b2 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 16:29:48 +0000 Subject: [PATCH 07/11] add py.typed flag file --- py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 py.typed diff --git a/py.typed b/py.typed new file mode 100644 index 0000000..e69de29 From ef8e3e8b59b9b8857563676fdc1d0868b98d25bb Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 31 Oct 2024 16:37:01 +0000 Subject: [PATCH 08/11] move py.typed into pkg and include in pyproject.toml --- pyproject.toml | 4 ++++ py.typed => web3_multi_provider/py.typed | 0 2 files changed, 4 insertions(+) rename py.typed => web3_multi_provider/py.typed (100%) diff --git a/pyproject.toml b/pyproject.toml index 81f2a6c..4d9b90a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,10 @@ readme = "README.md" include = [ "LICENSE", ] +packages = [ + {include = "web3_multi_provider"}, + {include = "web3_multi_provider/py.typed"}, +] [tool.poetry.dependencies] python = ">=3.12,<4" diff --git a/py.typed b/web3_multi_provider/py.typed similarity index 100% rename from py.typed rename to web3_multi_provider/py.typed From c3bcd09a29f065b4e107305b737799289f180908 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Fri, 1 Nov 2024 13:33:00 +0000 Subject: [PATCH 09/11] fix up tests and factor out common mocked functions --- pyproject.toml | 3 + tests/mocked_requests.py | 42 +++++++ tests/test_async_http_provider.py | 119 ++++++++---------- tests/test_http_provider.py | 67 +++++----- .../async_multi_http_provider.py | 2 +- 5 files changed, 138 insertions(+), 95 deletions(-) create mode 100644 tests/mocked_requests.py diff --git a/pyproject.toml b/pyproject.toml index 4d9b90a..4d4e96e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,3 +30,6 @@ pytest-mock = "^3.14.0" [tool.isort] profile = "black" + +[tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "function" diff --git a/tests/mocked_requests.py b/tests/mocked_requests.py new file mode 100644 index 0000000..6ca3bcd --- /dev/null +++ b/tests/mocked_requests.py @@ -0,0 +1,42 @@ +_MOCK_REQUEST_GET_RESULT = b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x486976656f6e2065752d68656176792d32", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' + + +def mocked_request_get( + endpoint_uri, + data, + *args, + **kwargs, +): + if "http://127.0.0.1:9000" in endpoint_uri: + return _MOCK_REQUEST_GET_RESULT + raise ConnectionError("Mocked connection error.") + + +async def mocked_async_request_get( + endpoint_uri, + data, + *args, + **kwargs, +): + return mocked_request_get(endpoint_uri, data, *args, **kwargs) + + +_MOCK_REQUEST_POA_RESULT = b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x00000000000000000000000051396620476f65726c6920417574686f72697479a8766f851c7ae7b3be68b8766225f28c8a0daf86bcdcdc7cb6a2cadec54bd393506e7d2088192110067a6d6280b13a2430d6b44dd2dbbe93d190ddce4309b83500", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' + + +def mocked_request_poa( + endpoint_uri, + data, + *args, + **kwargs, +): + return _MOCK_REQUEST_POA_RESULT + + +async def mocked_async_request_poa( + endpoint_uri, + data, + *args, + **kwargs, +): + return mocked_request_poa(endpoint_uri, data, *args, **kwargs) diff --git a/tests/test_async_http_provider.py b/tests/test_async_http_provider.py index 69641d4..56313b9 100644 --- a/tests/test_async_http_provider.py +++ b/tests/test_async_http_provider.py @@ -1,5 +1,4 @@ import logging -from unittest import TestCase from unittest.mock import Mock, patch import pytest @@ -11,42 +10,28 @@ NoActiveProviderError, ProtocolNotSupported, ) +from tests.mocked_requests import mocked_async_request_get, mocked_async_request_poa -async def mocked_requests_get( - endpoint_uri, - data, - *args, - **kwargs, -): - if "http://127.0.0.1:9000" in endpoint_uri: - return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x486976656f6e2065752d68656176792d32", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' - - raise ConnectionError("Mocked connection error.") - - -async def mocked_request_poa( - endpoint_uri, - data, - *args, - **kwargs, -): - return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x00000000000000000000000051396620476f65726c6920417574686f72697479a8766f851c7ae7b3be68b8766225f28c8a0daf86bcdcdc7cb6a2cadec54bd393506e7d2088192110067a6d6280b13a2430d6b44dd2dbbe93d190ddce4309b83500", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' - - -class HttpProviderTestCase(TestCase): +class TestHttpProvider: @pytest.fixture(autouse=True) def __inject_fixtures(self, caplog): self._caplog = caplog + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_one_provider_works(self, make_post_request): await self.one_provider_works(AsyncMultiProvider) await self.one_provider_works(AsyncFallbackProvider) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_nothing_works(self, make_post_request): self._caplog.set_level(logging.WARNING) @@ -60,11 +45,11 @@ async def test_nothing_works(self, make_post_request): w3 = AsyncWeb3(provider) with self._caplog.at_level(logging.DEBUG): - with self.assertRaises(NoActiveProviderError): + with pytest.raises(NoActiveProviderError): await w3.eth.get_block("latest") # Make sure there is no inf recursion - self.assertEqual(len(self._caplog.records), 6) + assert len(self._caplog.records) == 6 async def one_provider_works(self, provider_class): provider = provider_class( @@ -80,46 +65,42 @@ async def one_provider_works(self, provider_class): await w3.eth.get_block("latest") await w3.eth.get_block("latest") - self.assertDictEqual( - { + assert self._caplog.records[2].msg == { "msg": "Provider not responding.", "error": "Mocked connection error.", - }, - self._caplog.records[2].msg, - ) - self.assertDictEqual( - { + } + + assert self._caplog.records[5].msg == { "msg": "Send request using AsyncMultiProvider.", "method": "eth_getBlockByNumber", "params": "('latest', False)", - }, - self._caplog.records[5].msg, - ) + } + # Make sure second request will be directory to second provider and will ignore second one - self.assertDictEqual( - { + assert self._caplog.records[9].msg == { "msg": "Send request using AsyncMultiProvider.", "method": "eth_getBlockByNumber", "params": "('latest', False)", - }, - self._caplog.records[9].msg, - ) + } def test_protocols_support(self): AsyncMultiProvider(["http://127.0.0.1:9001"]) AsyncMultiProvider(["https://127.0.0.1:9001"]) - with self.assertRaises(ProtocolNotSupported): + with pytest.raises(ProtocolNotSupported): AsyncMultiProvider(["ipc://127.0.0.1:9001"]) - with self.assertRaises(ProtocolNotSupported): + with pytest.raises(ProtocolNotSupported): AsyncMultiProvider(["ws://127.0.0.1:9001"]) - with self.assertRaises(ProtocolNotSupported): + with pytest.raises(ProtocolNotSupported): AsyncMultiProvider(["wss://127.0.0.1:9001"]) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_poa, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_request_poa) async def test_poa_blockchain(self, make_post_request): provider = AsyncMultiProvider(["http://127.0.0.1:9000"]) @@ -128,15 +109,14 @@ async def test_poa_blockchain(self, make_post_request): with self._caplog.at_level(logging.DEBUG): block = await w3.eth.get_block("latest") - self.assertIn( - {"msg": "PoA blockchain cleanup response."}, - [log.msg for log in self._caplog.records], - ) - - self.assertIsNotNone(block.get("proofOfAuthorityData", None)) + assert {"msg": "PoA blockchain cleanup response."} in [log.msg for log in self._caplog.records] + assert block.get("proofOfAuthorityData") is not None + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_pos_blockchain(self, make_post_request): provider = AsyncMultiProvider(["http://127.0.0.1:9000"]) @@ -145,12 +125,8 @@ async def test_pos_blockchain(self, make_post_request): with self._caplog.at_level(logging.DEBUG): block = await w3.eth.get_block("latest") - self.assertIsNone(block.get("proofOfAuthorityData", None)) - - self.assertNotIn( - {"msg": "PoA blockchain cleanup response."}, - [log.msg for log in self._caplog.records], - ) + assert {"msg": "PoA blockchain cleanup response."} not in [log.msg for log in self._caplog.records] + assert block.get("proofOfAuthorityData") is None class TestAsyncFallbackProvider: @@ -161,8 +137,11 @@ async def test_no_endpoints(self): with pytest.raises(NoActiveProviderError): await w3.eth.get_block("latest") + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_one_endpoint(self, make_post_request: Mock): w3 = AsyncWeb3( AsyncFallbackProvider( @@ -175,8 +154,11 @@ async def test_one_endpoint(self, make_post_request: Mock): await w3.eth.get_block("latest") make_post_request.assert_called_once() + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_first_working(self, make_post_request: Mock): w3 = AsyncWeb3( AsyncFallbackProvider( @@ -191,8 +173,11 @@ async def test_first_working(self, make_post_request: Mock): make_post_request.assert_called_once() assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_all_endpoints_fail(self, make_post_request: Mock): w3 = AsyncWeb3( AsyncFallbackProvider( @@ -211,8 +196,11 @@ async def test_all_endpoints_fail(self, make_post_request: Mock): assert make_post_request.call_count == 3 assert make_post_request.call_args.args[0] == "http://127.0.0.1:9003" + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_one_endpoint_works(self, make_post_request: Mock): w3 = AsyncWeb3( AsyncFallbackProvider( @@ -228,8 +216,11 @@ async def test_one_endpoint_works(self, make_post_request: Mock): assert make_post_request.call_count == 2 assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", + side_effect=mocked_async_request_get, + ) @pytest.mark.asyncio - @patch("web3._utils.http_session_manager.HTTPSessionManager.async_make_post_request", side_effect=mocked_requests_get) async def test_starts_from_the_first(self, make_post_request: Mock): w3 = AsyncWeb3( AsyncFallbackProvider( diff --git a/tests/test_http_provider.py b/tests/test_http_provider.py index 8f0e0fa..e029e2f 100644 --- a/tests/test_http_provider.py +++ b/tests/test_http_provider.py @@ -11,27 +11,7 @@ NoActiveProviderError, ProtocolNotSupported, ) - - -def mocked_requests_get( - endpoint_uri, - data, - *args, - **kwargs, -): - if "http://127.0.0.1:9000" in endpoint_uri: - return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x486976656f6e2065752d68656176792d32", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' - - raise ConnectionError("Mocked connection error.") - - -def mocked_request_poa( - endpoint_uri, - data, - *args, - **kwargs, -): - return b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x00000000000000000000000051396620476f65726c6920417574686f72697479a8766f851c7ae7b3be68b8766225f28c8a0daf86bcdcdc7cb6a2cadec54bd393506e7d2088192110067a6d6280b13a2430d6b44dd2dbbe93d190ddce4309b83500", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' +from tests.mocked_requests import mocked_request_get, mocked_request_poa class HttpProviderTestCase(TestCase): @@ -39,12 +19,18 @@ class HttpProviderTestCase(TestCase): def __inject_fixtures(self, caplog): self._caplog = caplog - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_one_provider_works(self, make_post_request): self.one_provider_works(MultiProvider) self.one_provider_works(FallbackProvider) - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_nothing_works(self, make_post_request): self._caplog.set_level(logging.WARNING) @@ -116,7 +102,10 @@ def test_protocols_support(self): with self.assertRaises(ProtocolNotSupported): MultiProvider(["wss://127.0.0.1:9001"]) - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_request_poa) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_poa, + ) def test_poa_blockchain(self, make_post_request): provider = MultiProvider(["http://127.0.0.1:9000"]) @@ -132,7 +121,10 @@ def test_poa_blockchain(self, make_post_request): self.assertIsNotNone(block.get("proofOfAuthorityData", None)) - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_pos_blockchain(self, make_post_request): provider = MultiProvider(["http://127.0.0.1:9000"]) @@ -156,7 +148,10 @@ def test_no_endpoints(self): with pytest.raises(NoActiveProviderError): w3.eth.get_block("latest") - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_one_endpoint(self, make_post_request: Mock): w3 = Web3( FallbackProvider( @@ -169,7 +164,10 @@ def test_one_endpoint(self, make_post_request: Mock): w3.eth.get_block("latest") make_post_request.assert_called_once() - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_first_working(self, make_post_request: Mock): w3 = Web3( FallbackProvider( @@ -184,7 +182,10 @@ def test_first_working(self, make_post_request: Mock): make_post_request.assert_called_once() assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_all_endpoints_fail(self, make_post_request: Mock): w3 = Web3( FallbackProvider( @@ -203,7 +204,10 @@ def test_all_endpoints_fail(self, make_post_request: Mock): assert make_post_request.call_count == 3 assert make_post_request.call_args.args[0] == "http://127.0.0.1:9003" - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_one_endpoint_works(self, make_post_request: Mock): w3 = Web3( FallbackProvider( @@ -219,7 +223,10 @@ def test_one_endpoint_works(self, make_post_request: Mock): assert make_post_request.call_count == 2 assert make_post_request.call_args.args[0] == "http://127.0.0.1:9000" - @patch("web3._utils.http_session_manager.HTTPSessionManager.make_post_request", side_effect=mocked_requests_get) + @patch( + "web3._utils.http_session_manager.HTTPSessionManager.make_post_request", + side_effect=mocked_request_get, + ) def test_starts_from_the_first(self, make_post_request: Mock): w3 = Web3( FallbackProvider( diff --git a/web3_multi_provider/async_multi_http_provider.py b/web3_multi_provider/async_multi_http_provider.py index 1911359..7b27db4 100644 --- a/web3_multi_provider/async_multi_http_provider.py +++ b/web3_multi_provider/async_multi_http_provider.py @@ -86,7 +86,7 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: logger.debug( { - "msg": "Send request using MultiProvider.", + "msg": "Send request using AsyncMultiProvider.", "method": method, "params": str(params), } From 2a4131a67100c3f7bc26864e9640ddac9ba1692a Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Fri, 1 Nov 2024 13:41:55 +0000 Subject: [PATCH 10/11] fix up black, isort and pylint --- .pylintrc | 7 ---- tests/mocked_requests.py | 2 ++ tests/test_async_http_provider.py | 36 +++++++++++-------- tests/test_http_provider.py | 2 +- web3_multi_provider/__init__.py | 17 ++------- .../async_multi_http_provider.py | 4 +-- web3_multi_provider/exceptions.py | 2 -- web3_multi_provider/multi_http_provider.py | 2 +- web3_multi_provider/poa.py | 7 ++-- 9 files changed, 34 insertions(+), 45 deletions(-) diff --git a/.pylintrc b/.pylintrc index 61f3f61..f2f685b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -243,13 +243,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no diff --git a/tests/mocked_requests.py b/tests/mocked_requests.py index 6ca3bcd..9dd41e6 100644 --- a/tests/mocked_requests.py +++ b/tests/mocked_requests.py @@ -1,3 +1,4 @@ +# pylint: disable=line-too-long _MOCK_REQUEST_GET_RESULT = b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x486976656f6e2065752d68656176792d32", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' @@ -21,6 +22,7 @@ async def mocked_async_request_get( return mocked_request_get(endpoint_uri, data, *args, **kwargs) +# pylint: disable=line-too-long _MOCK_REQUEST_POA_RESULT = b'{"jsonrpc": "2.0", "id": 0, "result": {"baseFeePerGas": "0x1816eeb4af", "difficulty": "0x27aca623255aa9", "extraData": "0x00000000000000000000000051396620476f65726c6920417574686f72697479a8766f851c7ae7b3be68b8766225f28c8a0daf86bcdcdc7cb6a2cadec54bd393506e7d2088192110067a6d6280b13a2430d6b44dd2dbbe93d190ddce4309b83500", "gasLimit": "0x1c9c364", "gasUsed": "0x8454f8", "hash": "0x2420cd3a3f572ba42a881457c88c5c3f58cf44a46e7f25aea53d3a7313922694", "logsBloom": "0x5260400700da108048d93200854128614100802800081404698960db80801ec8464604e3940845a34200c3c2800a4971922881803c01a13902474008842a2518012005042016030f1a80920a703402e209a160240845ce0128a451c282e040c0be0401180a3a0608a355581942010aa06441180242406622780550b4460a02004c11890083047425054a21690dcc044450012c7389089a0a0c20674c419008840b790700804034120a000fc08c2394019087886200038c440c8124b090850d11404120a2818840537b410143518037f000006a0b063a2438148c25020023e606b81825ad000202011506060096a080810459c904a1000062108b191212013223", "miner": "0x1ad91ee08f21be3de0ba2ba6918e714da6b45836", "mixHash": "0x4889052c97da7a2386244f85d8061a0765e1c0f98a212c2eda929dc406713dbe", "nonce": "0xf078269bfcafc704", "number": "0xd1271e", "parentHash": "0x857a1b6ee3a8f2b837f9ae69f5a0b1b181903f5e23df0ed9d166776333e14ec8", "receiptsRoot": "0x1760ed19deceff14ce9cdfebd18ff257c3afc22359edb144d663cd6eb6523aba", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "size": "0xa1cc", "stateRoot": "0xa9d6469a301598d5de4f43f121c020da2814875c9154fffb72fb7a6edb8a88cd", "timestamp": "0x61a4733a", "totalDifficulty": "0x78150c58dfe2dc7fcb1", "transactions": [], "transactionsRoot": "0x46fdbd1a3ea670807040dd45f91ebe007c9521395d219150c2d8ac8b77055722", "uncles": []}}' diff --git a/tests/test_async_http_provider.py b/tests/test_async_http_provider.py index 56313b9..ab95b82 100644 --- a/tests/test_async_http_provider.py +++ b/tests/test_async_http_provider.py @@ -4,16 +4,18 @@ import pytest from web3 import AsyncWeb3 +from tests.mocked_requests import mocked_async_request_get, mocked_async_request_poa from web3_multi_provider import ( - AsyncMultiProvider, AsyncFallbackProvider, + AsyncMultiProvider, NoActiveProviderError, ProtocolNotSupported, ) -from tests.mocked_requests import mocked_async_request_get, mocked_async_request_poa class TestHttpProvider: + _caplog = None + @pytest.fixture(autouse=True) def __inject_fixtures(self, caplog): self._caplog = caplog @@ -66,22 +68,22 @@ async def one_provider_works(self, provider_class): await w3.eth.get_block("latest") assert self._caplog.records[2].msg == { - "msg": "Provider not responding.", - "error": "Mocked connection error.", - } + "msg": "Provider not responding.", + "error": "Mocked connection error.", + } assert self._caplog.records[5].msg == { - "msg": "Send request using AsyncMultiProvider.", - "method": "eth_getBlockByNumber", - "params": "('latest', False)", - } + "msg": "Send request using AsyncMultiProvider.", + "method": "eth_getBlockByNumber", + "params": "('latest', False)", + } # Make sure second request will be directory to second provider and will ignore second one assert self._caplog.records[9].msg == { - "msg": "Send request using AsyncMultiProvider.", - "method": "eth_getBlockByNumber", - "params": "('latest', False)", - } + "msg": "Send request using AsyncMultiProvider.", + "method": "eth_getBlockByNumber", + "params": "('latest', False)", + } def test_protocols_support(self): AsyncMultiProvider(["http://127.0.0.1:9001"]) @@ -109,7 +111,9 @@ async def test_poa_blockchain(self, make_post_request): with self._caplog.at_level(logging.DEBUG): block = await w3.eth.get_block("latest") - assert {"msg": "PoA blockchain cleanup response."} in [log.msg for log in self._caplog.records] + assert {"msg": "PoA blockchain cleanup response."} in [ + log.msg for log in self._caplog.records + ] assert block.get("proofOfAuthorityData") is not None @patch( @@ -125,7 +129,9 @@ async def test_pos_blockchain(self, make_post_request): with self._caplog.at_level(logging.DEBUG): block = await w3.eth.get_block("latest") - assert {"msg": "PoA blockchain cleanup response."} not in [log.msg for log in self._caplog.records] + assert {"msg": "PoA blockchain cleanup response."} not in [ + log.msg for log in self._caplog.records + ] assert block.get("proofOfAuthorityData") is None diff --git a/tests/test_http_provider.py b/tests/test_http_provider.py index e029e2f..563c13b 100644 --- a/tests/test_http_provider.py +++ b/tests/test_http_provider.py @@ -5,13 +5,13 @@ import pytest from web3 import Web3 +from tests.mocked_requests import mocked_request_get, mocked_request_poa from web3_multi_provider import MultiProvider from web3_multi_provider.multi_http_provider import ( FallbackProvider, NoActiveProviderError, ProtocolNotSupported, ) -from tests.mocked_requests import mocked_request_get, mocked_request_poa class HttpProviderTestCase(TestCase): diff --git a/web3_multi_provider/__init__.py b/web3_multi_provider/__init__.py index f55045f..9d7fd1f 100644 --- a/web3_multi_provider/__init__.py +++ b/web3_multi_provider/__init__.py @@ -1,17 +1,6 @@ -from .multi_http_provider import ( - FallbackProvider, - MultiProvider, -) - -from .async_multi_http_provider import ( - AsyncFallbackProvider, - AsyncMultiProvider, -) - -from .exceptions import ( - NoActiveProviderError, - ProtocolNotSupported, -) +from .async_multi_http_provider import AsyncFallbackProvider, AsyncMultiProvider +from .exceptions import NoActiveProviderError, ProtocolNotSupported +from .multi_http_provider import FallbackProvider, MultiProvider __all__ = ( "FallbackProvider", diff --git a/web3_multi_provider/async_multi_http_provider.py b/web3_multi_provider/async_multi_http_provider.py index 7b27db4..265b089 100644 --- a/web3_multi_provider/async_multi_http_provider.py +++ b/web3_multi_provider/async_multi_http_provider.py @@ -1,3 +1,4 @@ +# pylint: disable=duplicate-code import logging from abc import ABC from typing import Any @@ -9,13 +10,12 @@ from web3.providers.rpc.utils import ExceptionRetryConfiguration from web3.types import RPCEndpoint, RPCResponse -from web3_multi_provider.poa import sanitize_poa_response from web3_multi_provider.exceptions import NoActiveProviderError, ProtocolNotSupported +from web3_multi_provider.poa import sanitize_poa_response logger = logging.getLogger(__name__) - class AsyncBaseMultiProvider(AsyncJSONBaseProvider, ABC): """Base async provider for providers with multiple endpoints""" diff --git a/web3_multi_provider/exceptions.py b/web3_multi_provider/exceptions.py index b1fee77..3235183 100644 --- a/web3_multi_provider/exceptions.py +++ b/web3_multi_provider/exceptions.py @@ -4,5 +4,3 @@ class NoActiveProviderError(Exception): class ProtocolNotSupported(Exception): """Supported protocols: http, https""" - - diff --git a/web3_multi_provider/multi_http_provider.py b/web3_multi_provider/multi_http_provider.py index 91cff76..88bc9ed 100644 --- a/web3_multi_provider/multi_http_provider.py +++ b/web3_multi_provider/multi_http_provider.py @@ -9,8 +9,8 @@ from web3.providers.rpc.utils import ExceptionRetryConfiguration from web3.types import RPCEndpoint, RPCResponse -from web3_multi_provider.poa import sanitize_poa_response from web3_multi_provider.exceptions import NoActiveProviderError, ProtocolNotSupported +from web3_multi_provider.poa import sanitize_poa_response logger = logging.getLogger(__name__) diff --git a/web3_multi_provider/poa.py b/web3_multi_provider/poa.py index be21e88..807f7ce 100644 --- a/web3_multi_provider/poa.py +++ b/web3_multi_provider/poa.py @@ -1,14 +1,16 @@ import logging -from web3.types import RPCEndpoint, RPCResponse from web3._utils.rpc_abi import RPC -from web3.middleware.validation import _check_extradata_length from web3.exceptions import ExtraDataLengthError from web3.middleware.proof_of_authority import extradata_to_poa_cleanup +from web3.middleware.validation import _check_extradata_length +from web3.types import RPCEndpoint, RPCResponse logger = logging.getLogger(__name__) + def sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: + """Modify the response to remove PoA specific data.""" if method in (RPC.eth_getBlockByHash, RPC.eth_getBlockByNumber): if ( "result" in response @@ -21,4 +23,3 @@ def sanitize_poa_response(method: RPCEndpoint, response: RPCResponse) -> None: except ExtraDataLengthError: logger.debug({"msg": "PoA blockchain cleanup response."}) response["result"] = extradata_to_poa_cleanup(response["result"]) - From 11b66daeb6307c2194bb3c60eef3e70a3dcafcd7 Mon Sep 17 00:00:00 2001 From: F4ever Date: Fri, 1 Nov 2024 22:33:26 +0100 Subject: [PATCH 11/11] Fix sync tests duration --- .github/workflows/tests.yml | 2 +- tests/test_http_provider.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e07dabe..a5d455f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,4 +32,4 @@ jobs: uses: JRubics/poetry-publish@v2.0 with: pypi_token: ${{ secrets.PYPI_TOKEN }} - poetry_publish_options: "--dry-run" \ No newline at end of file + poetry_publish_options: "--dry-run" diff --git a/tests/test_http_provider.py b/tests/test_http_provider.py index 563c13b..a73d314 100644 --- a/tests/test_http_provider.py +++ b/tests/test_http_provider.py @@ -38,7 +38,8 @@ def test_nothing_works(self, make_post_request): [ "http://127.0.0.1:9001", "http://127.0.0.1:9002", - ] + ], + exception_retry_configuration=None, ) w3 = Web3(provider) @@ -55,7 +56,8 @@ def one_provider_works(self, provider_class): [ "http://127.0.0.1:9001", "http://127.0.0.1:9000", - ] + ], + exception_retry_configuration=None, ) w3 = Web3(provider)