web3 7.0.0b1__py3-none-any.whl → 7.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ens/__init__.py +13 -2
- ens/_normalization.py +4 -4
- ens/async_ens.py +31 -21
- ens/base_ens.py +3 -1
- ens/contract_data.py +2 -2
- ens/ens.py +14 -11
- ens/exceptions.py +16 -29
- ens/specs/nf.json +1 -1
- ens/specs/normalization_spec.json +1 -1
- ens/utils.py +33 -41
- web3/__init__.py +23 -12
- web3/_utils/abi.py +162 -274
- web3/_utils/async_transactions.py +34 -20
- web3/_utils/batching.py +217 -0
- web3/_utils/blocks.py +6 -2
- web3/_utils/caching/__init__.py +12 -0
- web3/_utils/caching/caching_utils.py +433 -0
- web3/_utils/caching/request_caching_validation.py +287 -0
- web3/_utils/compat/__init__.py +2 -3
- web3/_utils/contract_sources/compile_contracts.py +1 -1
- web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +42 -0
- web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
- web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
- web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
- web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/event_contracts.py +50 -5
- web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
- web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
- web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
- web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
- web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
- web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
- web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
- web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
- web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
- web3/_utils/contracts.py +172 -220
- web3/_utils/datatypes.py +5 -1
- web3/_utils/decorators.py +6 -1
- web3/_utils/empty.py +1 -1
- web3/_utils/encoding.py +16 -12
- web3/_utils/error_formatters_utils.py +5 -3
- web3/_utils/events.py +78 -72
- web3/_utils/fee_utils.py +1 -3
- web3/_utils/filters.py +24 -22
- web3/_utils/formatters.py +2 -2
- web3/_utils/http.py +8 -2
- web3/_utils/http_session_manager.py +314 -0
- web3/_utils/math.py +14 -15
- web3/_utils/method_formatters.py +161 -34
- web3/_utils/module.py +2 -1
- web3/_utils/module_testing/__init__.py +3 -2
- web3/_utils/module_testing/eth_module.py +736 -583
- web3/_utils/module_testing/go_ethereum_debug_module.py +128 -0
- web3/_utils/module_testing/module_testing_utils.py +81 -24
- web3/_utils/module_testing/persistent_connection_provider.py +702 -220
- web3/_utils/module_testing/utils.py +114 -33
- web3/_utils/module_testing/web3_module.py +438 -17
- web3/_utils/normalizers.py +13 -11
- web3/_utils/rpc_abi.py +10 -22
- web3/_utils/threads.py +8 -7
- web3/_utils/transactions.py +32 -25
- web3/_utils/type_conversion.py +5 -1
- web3/_utils/validation.py +20 -17
- web3/beacon/__init__.py +5 -0
- web3/beacon/api_endpoints.py +3 -0
- web3/beacon/async_beacon.py +29 -6
- web3/beacon/beacon.py +24 -6
- web3/contract/__init__.py +7 -0
- web3/contract/async_contract.py +285 -82
- web3/contract/base_contract.py +556 -258
- web3/contract/contract.py +295 -84
- web3/contract/utils.py +251 -55
- web3/datastructures.py +56 -41
- web3/eth/__init__.py +7 -0
- web3/eth/async_eth.py +89 -69
- web3/eth/base_eth.py +7 -3
- web3/eth/eth.py +43 -66
- web3/exceptions.py +158 -83
- web3/gas_strategies/time_based.py +8 -6
- web3/geth.py +53 -184
- web3/main.py +77 -43
- web3/manager.py +368 -101
- web3/method.py +43 -15
- web3/middleware/__init__.py +26 -8
- web3/middleware/attrdict.py +12 -22
- web3/middleware/base.py +55 -2
- web3/middleware/filter.py +45 -23
- web3/middleware/formatting.py +6 -3
- web3/middleware/names.py +4 -1
- web3/middleware/signing.py +15 -6
- web3/middleware/stalecheck.py +2 -1
- web3/module.py +62 -26
- web3/providers/__init__.py +21 -0
- web3/providers/async_base.py +93 -38
- web3/providers/base.py +85 -40
- web3/providers/eth_tester/__init__.py +5 -0
- web3/providers/eth_tester/defaults.py +2 -55
- web3/providers/eth_tester/main.py +57 -35
- web3/providers/eth_tester/middleware.py +16 -17
- web3/providers/ipc.py +42 -18
- web3/providers/legacy_websocket.py +27 -2
- web3/providers/persistent/__init__.py +7 -0
- web3/providers/persistent/async_ipc.py +61 -121
- web3/providers/persistent/persistent.py +324 -17
- web3/providers/persistent/persistent_connection.py +54 -5
- web3/providers/persistent/request_processor.py +136 -56
- web3/providers/persistent/subscription_container.py +56 -0
- web3/providers/persistent/subscription_manager.py +233 -0
- web3/providers/persistent/websocket.py +29 -92
- web3/providers/rpc/__init__.py +5 -0
- web3/providers/rpc/async_rpc.py +73 -18
- web3/providers/rpc/rpc.py +73 -30
- web3/providers/rpc/utils.py +1 -13
- web3/scripts/install_pre_releases.py +33 -0
- web3/scripts/parse_pygeth_version.py +16 -0
- web3/testing.py +4 -4
- web3/tracing.py +9 -5
- web3/types.py +141 -74
- web3/utils/__init__.py +64 -5
- web3/utils/abi.py +790 -10
- web3/utils/address.py +8 -0
- web3/utils/async_exception_handling.py +20 -11
- web3/utils/caching.py +34 -4
- web3/utils/exception_handling.py +9 -12
- web3/utils/subscriptions.py +285 -0
- {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/LICENSE +1 -1
- web3-7.7.0.dist-info/METADATA +130 -0
- web3-7.7.0.dist-info/RECORD +171 -0
- {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/WHEEL +1 -1
- {web3-7.0.0b1.dist-info → web3-7.7.0.dist-info}/top_level.txt +0 -1
- ethpm/__init__.py +0 -20
- ethpm/_utils/__init__.py +0 -0
- ethpm/_utils/backend.py +0 -93
- ethpm/_utils/cache.py +0 -44
- ethpm/_utils/chains.py +0 -119
- ethpm/_utils/contract.py +0 -35
- ethpm/_utils/deployments.py +0 -145
- ethpm/_utils/ipfs.py +0 -116
- ethpm/_utils/protobuf/__init__.py +0 -0
- ethpm/_utils/protobuf/ipfs_file_pb2.py +0 -33
- ethpm/_utils/registry.py +0 -29
- ethpm/assets/__init__.py +0 -0
- ethpm/assets/ens/v3.json +0 -1
- ethpm/assets/escrow/with_bytecode_v3.json +0 -1
- ethpm/assets/ipfs_file.proto +0 -32
- ethpm/assets/owned/output_v3.json +0 -1
- ethpm/assets/owned/with_contract_type_v3.json +0 -1
- ethpm/assets/registry/contracts/Authority.sol +0 -156
- ethpm/assets/registry/contracts/IndexedOrderedSetLib.sol +0 -106
- ethpm/assets/registry/contracts/PackageDB.sol +0 -225
- ethpm/assets/registry/contracts/PackageRegistry.sol +0 -361
- ethpm/assets/registry/contracts/PackageRegistryInterface.sol +0 -97
- ethpm/assets/registry/contracts/ReleaseDB.sol +0 -309
- ethpm/assets/registry/contracts/ReleaseValidator.sol +0 -152
- ethpm/assets/registry/solc_input.json +0 -1
- ethpm/assets/registry/solc_output.json +0 -1
- ethpm/assets/registry/v3.json +0 -1
- ethpm/assets/safe-math-lib/v3-strict-no-deployments.json +0 -1
- ethpm/assets/simple-registry/contracts/Ownable.sol +0 -63
- ethpm/assets/simple-registry/contracts/PackageRegistry.sol +0 -373
- ethpm/assets/simple-registry/contracts/PackageRegistryInterface.sol +0 -96
- ethpm/assets/simple-registry/solc_input.json +0 -33
- ethpm/assets/simple-registry/solc_output.json +0 -1
- ethpm/assets/simple-registry/v3.json +0 -1
- ethpm/assets/standard-token/output_v3.json +0 -1
- ethpm/assets/standard-token/with_bytecode_v3.json +0 -1
- ethpm/assets/vyper_registry/0.1.0.json +0 -1
- ethpm/assets/vyper_registry/registry.vy +0 -216
- ethpm/assets/vyper_registry/registry_with_delete.vy +0 -244
- ethpm/backends/__init__.py +0 -0
- ethpm/backends/base.py +0 -43
- ethpm/backends/http.py +0 -108
- ethpm/backends/ipfs.py +0 -219
- ethpm/backends/registry.py +0 -154
- ethpm/constants.py +0 -17
- ethpm/contract.py +0 -187
- ethpm/dependencies.py +0 -58
- ethpm/deployments.py +0 -80
- ethpm/ethpm-spec/examples/escrow/1.0.0-pretty.json +0 -146
- ethpm/ethpm-spec/examples/escrow/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/escrow/contracts/Escrow.sol +0 -32
- ethpm/ethpm-spec/examples/escrow/contracts/SafeSendLib.sol +0 -20
- ethpm/ethpm-spec/examples/escrow/v3-pretty.json +0 -171
- ethpm/ethpm-spec/examples/escrow/v3.json +0 -1
- ethpm/ethpm-spec/examples/owned/1.0.0-pretty.json +0 -21
- ethpm/ethpm-spec/examples/owned/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/owned/contracts/Owned.sol +0 -12
- ethpm/ethpm-spec/examples/owned/v3-pretty.json +0 -27
- ethpm/ethpm-spec/examples/owned/v3.json +0 -1
- ethpm/ethpm-spec/examples/piper-coin/1.0.0-pretty.json +0 -31
- ethpm/ethpm-spec/examples/piper-coin/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/piper-coin/v3-pretty.json +0 -21
- ethpm/ethpm-spec/examples/piper-coin/v3.json +0 -1
- ethpm/ethpm-spec/examples/safe-math-lib/1.0.0-pretty.json +0 -85
- ethpm/ethpm-spec/examples/safe-math-lib/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/safe-math-lib/contracts/SafeMathLib.sol +0 -24
- ethpm/ethpm-spec/examples/safe-math-lib/v3-pretty.json +0 -117
- ethpm/ethpm-spec/examples/safe-math-lib/v3.json +0 -1
- ethpm/ethpm-spec/examples/standard-token/1.0.0-pretty.json +0 -55
- ethpm/ethpm-spec/examples/standard-token/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/standard-token/contracts/AbstractToken.sol +0 -20
- ethpm/ethpm-spec/examples/standard-token/contracts/StandardToken.sol +0 -84
- ethpm/ethpm-spec/examples/standard-token/v3-pretty.json +0 -460
- ethpm/ethpm-spec/examples/standard-token/v3.json +0 -1
- ethpm/ethpm-spec/examples/transferable/1.0.0-pretty.json +0 -21
- ethpm/ethpm-spec/examples/transferable/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/transferable/contracts/Transferable.sol +0 -14
- ethpm/ethpm-spec/examples/transferable/v3-pretty.json +0 -27
- ethpm/ethpm-spec/examples/transferable/v3.json +0 -1
- ethpm/ethpm-spec/examples/wallet/1.0.0-pretty.json +0 -120
- ethpm/ethpm-spec/examples/wallet/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/wallet/contracts/Wallet.sol +0 -41
- ethpm/ethpm-spec/examples/wallet/v3-pretty.json +0 -181
- ethpm/ethpm-spec/examples/wallet/v3.json +0 -1
- ethpm/ethpm-spec/examples/wallet-with-send/1.0.0-pretty.json +0 -135
- ethpm/ethpm-spec/examples/wallet-with-send/1.0.0.json +0 -1
- ethpm/ethpm-spec/examples/wallet-with-send/contracts/WalletWithSend.sol +0 -18
- ethpm/ethpm-spec/examples/wallet-with-send/v3-pretty.json +0 -207
- ethpm/ethpm-spec/examples/wallet-with-send/v3.json +0 -1
- ethpm/ethpm-spec/spec/package.spec.json +0 -379
- ethpm/ethpm-spec/spec/v3.spec.json +0 -483
- ethpm/exceptions.py +0 -68
- ethpm/package.py +0 -438
- ethpm/tools/__init__.py +0 -4
- ethpm/tools/builder.py +0 -930
- ethpm/tools/checker.py +0 -312
- ethpm/tools/get_manifest.py +0 -19
- ethpm/uri.py +0 -141
- ethpm/validation/__init__.py +0 -0
- ethpm/validation/manifest.py +0 -146
- ethpm/validation/misc.py +0 -39
- ethpm/validation/package.py +0 -80
- ethpm/validation/uri.py +0 -163
- web3/_utils/caching.py +0 -155
- web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
- web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
- web3/_utils/request.py +0 -265
- web3/pm.py +0 -602
- web3/tools/__init__.py +0 -4
- web3/tools/benchmark/__init__.py +0 -0
- web3/tools/benchmark/main.py +0 -185
- web3/tools/benchmark/node.py +0 -126
- web3/tools/benchmark/reporting.py +0 -39
- web3/tools/benchmark/utils.py +0 -69
- web3/tools/pytest_ethereum/__init__.py +0 -0
- web3/tools/pytest_ethereum/_utils.py +0 -145
- web3/tools/pytest_ethereum/deployer.py +0 -48
- web3/tools/pytest_ethereum/exceptions.py +0 -22
- web3/tools/pytest_ethereum/linker.py +0 -128
- web3/tools/pytest_ethereum/plugins.py +0 -33
- web3-7.0.0b1.dist-info/METADATA +0 -114
- web3-7.0.0b1.dist-info/RECORD +0 -280
- web3-7.0.0b1.dist-info/entry_points.txt +0 -2
- /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
from abc import (
|
|
2
2
|
ABC,
|
|
3
|
+
abstractmethod,
|
|
3
4
|
)
|
|
4
5
|
import asyncio
|
|
5
6
|
import logging
|
|
7
|
+
import signal
|
|
6
8
|
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
Coroutine,
|
|
13
|
+
List,
|
|
7
14
|
Optional,
|
|
15
|
+
Tuple,
|
|
16
|
+
Union,
|
|
17
|
+
cast,
|
|
8
18
|
)
|
|
9
19
|
|
|
20
|
+
from websockets import (
|
|
21
|
+
ConnectionClosed,
|
|
22
|
+
WebSocketException,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from web3._utils.batching import (
|
|
26
|
+
BATCH_REQUEST_ID,
|
|
27
|
+
sort_batch_response_by_response_ids,
|
|
28
|
+
)
|
|
10
29
|
from web3._utils.caching import (
|
|
11
30
|
generate_cache_key,
|
|
12
31
|
)
|
|
32
|
+
from web3._utils.caching.caching_utils import (
|
|
33
|
+
async_handle_recv_caching,
|
|
34
|
+
async_handle_send_caching,
|
|
35
|
+
)
|
|
13
36
|
from web3.exceptions import (
|
|
37
|
+
PersistentConnectionClosedOK,
|
|
38
|
+
ProviderConnectionError,
|
|
39
|
+
TaskNotRunning,
|
|
14
40
|
TimeExhausted,
|
|
41
|
+
Web3AttributeError,
|
|
15
42
|
)
|
|
16
43
|
from web3.providers.async_base import (
|
|
17
44
|
AsyncJSONBaseProvider,
|
|
@@ -20,47 +47,327 @@ from web3.providers.persistent.request_processor import (
|
|
|
20
47
|
RequestProcessor,
|
|
21
48
|
)
|
|
22
49
|
from web3.types import (
|
|
50
|
+
RPCEndpoint,
|
|
23
51
|
RPCId,
|
|
52
|
+
RPCRequest,
|
|
24
53
|
RPCResponse,
|
|
25
54
|
)
|
|
26
55
|
|
|
27
|
-
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from web3 import AsyncWeb3 # noqa: F401
|
|
58
|
+
from web3.middleware.base import MiddlewareOnion # noqa: F401
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 30.0
|
|
28
62
|
|
|
29
63
|
|
|
30
64
|
class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
31
65
|
logger = logging.getLogger("web3.providers.PersistentConnectionProvider")
|
|
32
66
|
has_persistent_connection = True
|
|
33
|
-
endpoint_uri: Optional[str] = None
|
|
34
67
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
_send_func_cache: Tuple[int, Callable[..., Coroutine[Any, Any, RPCRequest]]] = (
|
|
69
|
+
None,
|
|
70
|
+
None,
|
|
71
|
+
)
|
|
72
|
+
_recv_func_cache: Tuple[int, Callable[..., Coroutine[Any, Any, RPCResponse]]] = (
|
|
73
|
+
None,
|
|
74
|
+
None,
|
|
75
|
+
)
|
|
38
76
|
|
|
39
77
|
def __init__(
|
|
40
78
|
self,
|
|
41
79
|
request_timeout: float = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT,
|
|
42
80
|
subscription_response_queue_size: int = 500,
|
|
43
81
|
silence_listener_task_exceptions: bool = False,
|
|
82
|
+
max_connection_retries: int = 5,
|
|
83
|
+
**kwargs: Any,
|
|
44
84
|
) -> None:
|
|
45
|
-
super().__init__()
|
|
85
|
+
super().__init__(**kwargs)
|
|
46
86
|
self._request_processor = RequestProcessor(
|
|
47
87
|
self,
|
|
48
88
|
subscription_response_queue_size=subscription_response_queue_size,
|
|
49
89
|
)
|
|
90
|
+
self._message_listener_task: Optional["asyncio.Task[None]"] = None
|
|
91
|
+
self._batch_request_counter: Optional[int] = None
|
|
92
|
+
self._listen_event: asyncio.Event = asyncio.Event()
|
|
93
|
+
self._max_connection_retries = max_connection_retries
|
|
94
|
+
|
|
50
95
|
self.request_timeout = request_timeout
|
|
51
96
|
self.silence_listener_task_exceptions = silence_listener_task_exceptions
|
|
52
97
|
|
|
98
|
+
async def send_func(
|
|
99
|
+
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
100
|
+
) -> Callable[..., Coroutine[Any, Any, RPCRequest]]:
|
|
101
|
+
"""
|
|
102
|
+
Cache the middleware chain for `send`.
|
|
103
|
+
"""
|
|
104
|
+
middleware = middleware_onion.as_tuple_of_middleware()
|
|
105
|
+
cache_key = hash(tuple(id(mw) for mw in middleware))
|
|
106
|
+
|
|
107
|
+
if cache_key != self._send_func_cache[0]:
|
|
108
|
+
|
|
109
|
+
async def send_function(method: RPCEndpoint, params: Any) -> RPCRequest:
|
|
110
|
+
for mw in middleware:
|
|
111
|
+
initialized = mw(async_w3)
|
|
112
|
+
method, params = await initialized.async_request_processor(
|
|
113
|
+
method, params
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return await self.send_request(method, params)
|
|
117
|
+
|
|
118
|
+
self._send_func_cache = (cache_key, send_function)
|
|
119
|
+
|
|
120
|
+
return self._send_func_cache[1]
|
|
121
|
+
|
|
122
|
+
async def recv_func(
|
|
123
|
+
self, async_w3: "AsyncWeb3", middleware_onion: "MiddlewareOnion"
|
|
124
|
+
) -> Any:
|
|
125
|
+
"""
|
|
126
|
+
Cache and compose the middleware stack for `recv`.
|
|
127
|
+
"""
|
|
128
|
+
middleware = middleware_onion.as_tuple_of_middleware()
|
|
129
|
+
cache_key = hash(tuple(id(mw) for mw in middleware))
|
|
130
|
+
|
|
131
|
+
if cache_key != self._recv_func_cache[0]:
|
|
132
|
+
|
|
133
|
+
async def recv_function(rpc_request: RPCRequest) -> RPCResponse:
|
|
134
|
+
# first, retrieve the response
|
|
135
|
+
response = await self.recv_for_request(rpc_request)
|
|
136
|
+
method = rpc_request["method"]
|
|
137
|
+
for mw in reversed(middleware):
|
|
138
|
+
initialized = mw(async_w3)
|
|
139
|
+
response = await initialized.async_response_processor(
|
|
140
|
+
method, response
|
|
141
|
+
)
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
self._recv_func_cache = (cache_key, recv_function)
|
|
145
|
+
|
|
146
|
+
return self._recv_func_cache[1]
|
|
147
|
+
|
|
148
|
+
def get_endpoint_uri_or_ipc_path(self) -> str:
|
|
149
|
+
if hasattr(self, "endpoint_uri"):
|
|
150
|
+
return str(self.endpoint_uri)
|
|
151
|
+
elif hasattr(self, "ipc_path"):
|
|
152
|
+
return str(self.ipc_path)
|
|
153
|
+
else:
|
|
154
|
+
raise Web3AttributeError(
|
|
155
|
+
"`PersistentConnectionProvider` must have either `endpoint_uri` or "
|
|
156
|
+
"`ipc_path` attribute."
|
|
157
|
+
)
|
|
158
|
+
|
|
53
159
|
async def connect(self) -> None:
|
|
54
|
-
|
|
160
|
+
_connection_attempts = 0
|
|
161
|
+
_backoff_rate_change = 1.75
|
|
162
|
+
_backoff_time = 1.75
|
|
163
|
+
|
|
164
|
+
while _connection_attempts != self._max_connection_retries:
|
|
165
|
+
try:
|
|
166
|
+
_connection_attempts += 1
|
|
167
|
+
self.logger.info(
|
|
168
|
+
f"Connecting to: {self.get_endpoint_uri_or_ipc_path()}"
|
|
169
|
+
)
|
|
170
|
+
await self._provider_specific_connect()
|
|
171
|
+
self._message_listener_task = asyncio.create_task(
|
|
172
|
+
self._message_listener()
|
|
173
|
+
)
|
|
174
|
+
self._message_listener_task.add_done_callback(
|
|
175
|
+
self._message_listener_callback
|
|
176
|
+
)
|
|
177
|
+
self.logger.info(
|
|
178
|
+
f"Successfully connected to: {self.get_endpoint_uri_or_ipc_path()}"
|
|
179
|
+
)
|
|
180
|
+
break
|
|
181
|
+
except (WebSocketException, OSError) as e:
|
|
182
|
+
if _connection_attempts == self._max_connection_retries:
|
|
183
|
+
raise ProviderConnectionError(
|
|
184
|
+
f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
|
|
185
|
+
f"Retries exceeded max of {self._max_connection_retries}."
|
|
186
|
+
) from e
|
|
187
|
+
self.logger.info(
|
|
188
|
+
f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
|
|
189
|
+
f"Retrying in {round(_backoff_time, 1)} seconds.",
|
|
190
|
+
exc_info=True,
|
|
191
|
+
)
|
|
192
|
+
await asyncio.sleep(_backoff_time)
|
|
193
|
+
_backoff_time *= _backoff_rate_change
|
|
55
194
|
|
|
56
195
|
async def disconnect(self) -> None:
|
|
196
|
+
# this should remain idempotent
|
|
197
|
+
try:
|
|
198
|
+
if self._message_listener_task:
|
|
199
|
+
self._message_listener_task.cancel()
|
|
200
|
+
await self._message_listener_task
|
|
201
|
+
except (asyncio.CancelledError, StopAsyncIteration, ConnectionClosed):
|
|
202
|
+
pass
|
|
203
|
+
finally:
|
|
204
|
+
self._message_listener_task = None
|
|
205
|
+
self.logger.info("Message listener background task successfully shut down.")
|
|
206
|
+
|
|
207
|
+
await self._provider_specific_disconnect()
|
|
208
|
+
self._request_processor.clear_caches()
|
|
209
|
+
self.logger.info(
|
|
210
|
+
f"Successfully disconnected from: {self.get_endpoint_uri_or_ipc_path()}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@async_handle_send_caching
|
|
214
|
+
async def send_request(self, method: RPCEndpoint, params: Any) -> RPCRequest:
|
|
215
|
+
request_dict = self.form_request(method, params)
|
|
216
|
+
await self.socket_send(self.encode_rpc_dict(request_dict))
|
|
217
|
+
return request_dict
|
|
218
|
+
|
|
219
|
+
@async_handle_recv_caching
|
|
220
|
+
async def recv_for_request(self, rpc_request: RPCRequest) -> RPCResponse:
|
|
221
|
+
return await self._get_response_for_request_id(rpc_request["id"])
|
|
222
|
+
|
|
223
|
+
async def make_request(
|
|
224
|
+
self,
|
|
225
|
+
method: RPCEndpoint,
|
|
226
|
+
params: Any,
|
|
227
|
+
) -> RPCResponse:
|
|
228
|
+
rpc_request = await self.send_request(method, params)
|
|
229
|
+
return await self.recv_for_request(rpc_request)
|
|
230
|
+
|
|
231
|
+
async def make_batch_request(
|
|
232
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
233
|
+
) -> List[RPCResponse]:
|
|
234
|
+
request_data = self.encode_batch_rpc_request(requests)
|
|
235
|
+
await self.socket_send(request_data)
|
|
236
|
+
|
|
237
|
+
response = cast(
|
|
238
|
+
List[RPCResponse], await self._get_response_for_request_id(BATCH_REQUEST_ID)
|
|
239
|
+
)
|
|
240
|
+
return response
|
|
241
|
+
|
|
242
|
+
# -- abstract methods -- #
|
|
243
|
+
|
|
244
|
+
@abstractmethod
|
|
245
|
+
async def socket_send(self, request_data: bytes) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Send an encoded RPC request to the provider over the persistent connection.
|
|
248
|
+
"""
|
|
57
249
|
raise NotImplementedError("Must be implemented by subclasses")
|
|
58
250
|
|
|
59
|
-
|
|
251
|
+
@abstractmethod
|
|
252
|
+
async def socket_recv(self) -> RPCResponse:
|
|
253
|
+
"""
|
|
254
|
+
Receive, decode, and return an RPC response from the provider over the
|
|
255
|
+
persistent connection.
|
|
256
|
+
"""
|
|
257
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
258
|
+
|
|
259
|
+
# -- private methods -- #
|
|
260
|
+
|
|
261
|
+
async def _provider_specific_connect(self) -> None:
|
|
60
262
|
raise NotImplementedError("Must be implemented by subclasses")
|
|
61
263
|
|
|
264
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
265
|
+
# this method should be idempotent
|
|
266
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
267
|
+
|
|
268
|
+
async def _provider_specific_socket_reader(self) -> RPCResponse:
|
|
269
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
270
|
+
|
|
271
|
+
def _set_signal_handlers(self) -> None:
|
|
272
|
+
def extended_handler(sig: int, frame: Any, existing_handler: Any) -> None:
|
|
273
|
+
loop = asyncio.get_event_loop()
|
|
274
|
+
|
|
275
|
+
# invoke the existing handler, if callable
|
|
276
|
+
if callable(existing_handler):
|
|
277
|
+
existing_handler(sig, frame)
|
|
278
|
+
loop.create_task(self.disconnect())
|
|
279
|
+
|
|
280
|
+
existing_sigint_handler = signal.getsignal(signal.SIGINT)
|
|
281
|
+
existing_sigterm_handler = signal.getsignal(signal.SIGTERM)
|
|
282
|
+
|
|
283
|
+
# extend the existing signal handlers to include the disconnect method
|
|
284
|
+
signal.signal(
|
|
285
|
+
signal.SIGINT,
|
|
286
|
+
lambda sig, frame: extended_handler(sig, frame, existing_sigint_handler),
|
|
287
|
+
)
|
|
288
|
+
signal.signal(
|
|
289
|
+
signal.SIGTERM,
|
|
290
|
+
lambda sig, frame: extended_handler(sig, frame, existing_sigterm_handler),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _message_listener_callback(
|
|
294
|
+
self, message_listener_task: "asyncio.Task[None]"
|
|
295
|
+
) -> None:
|
|
296
|
+
# Puts a `TaskNotRunning` in appropriate queues to signal the end of the
|
|
297
|
+
# listener task to any listeners relying on the queues.
|
|
298
|
+
self._request_processor._subscription_response_queue.put_nowait(
|
|
299
|
+
TaskNotRunning(message_listener_task)
|
|
300
|
+
)
|
|
301
|
+
self._request_processor._handler_subscription_queue.put_nowait(
|
|
302
|
+
TaskNotRunning(message_listener_task)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
async def _message_listener(self) -> None:
|
|
306
|
+
self.logger.info(
|
|
307
|
+
f"{self.__class__.__qualname__} listener background task started. Storing "
|
|
308
|
+
"all messages in appropriate request processor queues / caches to be "
|
|
309
|
+
"processed."
|
|
310
|
+
)
|
|
311
|
+
while True:
|
|
312
|
+
# the use of sleep(0) seems to be the most efficient way to yield control
|
|
313
|
+
# back to the event loop to share the loop with other tasks.
|
|
314
|
+
await asyncio.sleep(0)
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
response = await self._provider_specific_socket_reader()
|
|
318
|
+
|
|
319
|
+
if isinstance(response, list):
|
|
320
|
+
response = sort_batch_response_by_response_ids(response)
|
|
321
|
+
|
|
322
|
+
subscription = (
|
|
323
|
+
response.get("method") == "eth_subscription"
|
|
324
|
+
if not isinstance(response, list)
|
|
325
|
+
else False
|
|
326
|
+
)
|
|
327
|
+
await self._request_processor.cache_raw_response(
|
|
328
|
+
response, subscription=subscription
|
|
329
|
+
)
|
|
330
|
+
except PersistentConnectionClosedOK as e:
|
|
331
|
+
self.logger.info(
|
|
332
|
+
"Message listener background task has ended gracefully: "
|
|
333
|
+
f"{e.user_message}"
|
|
334
|
+
)
|
|
335
|
+
# trigger a return to end the listener task and initiate the callback fn
|
|
336
|
+
return
|
|
337
|
+
except Exception as e:
|
|
338
|
+
if not self.silence_listener_task_exceptions:
|
|
339
|
+
raise e
|
|
340
|
+
else:
|
|
341
|
+
self._error_log_listener_task_exception(e)
|
|
342
|
+
|
|
343
|
+
def _error_log_listener_task_exception(self, e: Exception) -> None:
|
|
344
|
+
"""
|
|
345
|
+
When silencing listener task exceptions, this method is used to log the
|
|
346
|
+
exception and keep the listener task alive. Override this method to fine-tune
|
|
347
|
+
error logging behavior for the implementation class.
|
|
348
|
+
"""
|
|
349
|
+
self.logger.error(
|
|
350
|
+
"Exception caught in listener, error logging and keeping "
|
|
351
|
+
"listener background task alive."
|
|
352
|
+
f"\n error={e.__class__.__name__}: {e}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _handle_listener_task_exceptions(self) -> None:
|
|
356
|
+
"""
|
|
357
|
+
Should be called every time a `PersistentConnectionProvider` is polling for
|
|
358
|
+
messages in the main loop. If the message listener task has completed and an
|
|
359
|
+
exception was recorded, raise the exception in the main loop.
|
|
360
|
+
"""
|
|
361
|
+
msg_listener_task = getattr(self, "_message_listener_task", None)
|
|
362
|
+
if (
|
|
363
|
+
msg_listener_task
|
|
364
|
+
and msg_listener_task.done()
|
|
365
|
+
and msg_listener_task.exception()
|
|
366
|
+
):
|
|
367
|
+
raise msg_listener_task.exception()
|
|
368
|
+
|
|
62
369
|
async def _get_response_for_request_id(
|
|
63
|
-
self, request_id: RPCId, timeout: Optional[float] = None
|
|
370
|
+
self, request_id: Union[RPCId, List[RPCId]], timeout: Optional[float] = None
|
|
64
371
|
) -> RPCResponse:
|
|
65
372
|
if timeout is None:
|
|
66
373
|
timeout = self.request_timeout
|
|
@@ -69,24 +376,24 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
69
376
|
request_cache_key = generate_cache_key(request_id)
|
|
70
377
|
|
|
71
378
|
while True:
|
|
72
|
-
# sleep(0) here seems to be the most efficient way to yield control
|
|
73
|
-
# back to the event loop while waiting for the response to be in the
|
|
74
|
-
# queue.
|
|
75
|
-
await asyncio.sleep(0)
|
|
76
|
-
|
|
77
379
|
if request_cache_key in self._request_processor._request_response_cache:
|
|
78
380
|
self.logger.debug(
|
|
79
381
|
f"Popping response for id {request_id} from cache."
|
|
80
382
|
)
|
|
81
|
-
popped_response = self._request_processor.pop_raw_response(
|
|
383
|
+
popped_response = await self._request_processor.pop_raw_response(
|
|
82
384
|
cache_key=request_cache_key,
|
|
83
385
|
)
|
|
84
386
|
return popped_response
|
|
387
|
+
else:
|
|
388
|
+
# check if an exception was recorded in the listener task and raise
|
|
389
|
+
# it in the main loop if so
|
|
390
|
+
self._handle_listener_task_exceptions()
|
|
391
|
+
await asyncio.sleep(0)
|
|
85
392
|
|
|
86
393
|
try:
|
|
87
394
|
# Add the request timeout around the while loop that checks the request
|
|
88
|
-
# cache
|
|
89
|
-
#
|
|
395
|
+
# cache. If the request is not in the cache within the request_timeout,
|
|
396
|
+
# raise ``TimeExhausted``.
|
|
90
397
|
return await asyncio.wait_for(_match_response_id_to_request_id(), timeout)
|
|
91
398
|
except asyncio.TimeoutError:
|
|
92
399
|
raise TimeExhausted(
|
|
@@ -2,9 +2,12 @@ from typing import (
|
|
|
2
2
|
TYPE_CHECKING,
|
|
3
3
|
Any,
|
|
4
4
|
Dict,
|
|
5
|
+
Union,
|
|
6
|
+
cast,
|
|
5
7
|
)
|
|
6
8
|
|
|
7
9
|
from web3.types import (
|
|
10
|
+
FormattedEthSubscriptionResponse,
|
|
8
11
|
RPCEndpoint,
|
|
9
12
|
RPCResponse,
|
|
10
13
|
)
|
|
@@ -16,6 +19,9 @@ if TYPE_CHECKING:
|
|
|
16
19
|
from web3.manager import ( # noqa: F401
|
|
17
20
|
_AsyncPersistentMessageStream,
|
|
18
21
|
)
|
|
22
|
+
from web3.providers.persistent import ( # noqa: F401
|
|
23
|
+
PersistentConnectionProvider,
|
|
24
|
+
)
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
class PersistentConnection:
|
|
@@ -26,17 +32,60 @@ class PersistentConnection:
|
|
|
26
32
|
|
|
27
33
|
def __init__(self, w3: "AsyncWeb3"):
|
|
28
34
|
self._manager = w3.manager
|
|
35
|
+
self.provider = cast("PersistentConnectionProvider", self._manager.provider)
|
|
29
36
|
|
|
30
|
-
# -- public methods -- #
|
|
31
37
|
@property
|
|
32
38
|
def subscriptions(self) -> Dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Return the active subscriptions on the persistent connection.
|
|
41
|
+
|
|
42
|
+
:return: The active subscriptions on the persistent connection.
|
|
43
|
+
:rtype: Dict[str, Any]
|
|
44
|
+
"""
|
|
33
45
|
return self._manager._request_processor.active_subscriptions
|
|
34
46
|
|
|
35
|
-
async def
|
|
36
|
-
|
|
47
|
+
async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
48
|
+
"""
|
|
49
|
+
Make a request to the persistent connection and return the response. This method
|
|
50
|
+
does not process the response as it would when invoking a method via the
|
|
51
|
+
appropriate module on the `AsyncWeb3` instance,
|
|
52
|
+
e.g. `w3.eth.get_block("latest")`.
|
|
53
|
+
|
|
54
|
+
:param method: The RPC method, e.g. `eth_getBlockByNumber`.
|
|
55
|
+
:param params: The RPC method parameters, e.g. `["0x1337", False]`.
|
|
56
|
+
|
|
57
|
+
:return: The unprocessed response from the persistent connection.
|
|
58
|
+
:rtype: RPCResponse
|
|
59
|
+
"""
|
|
60
|
+
return await self.provider.make_request(method, params)
|
|
61
|
+
|
|
62
|
+
async def send(self, method: RPCEndpoint, params: Any) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Send a raw, unprocessed message to the persistent connection.
|
|
37
65
|
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
:param method: The RPC method, e.g. `eth_getBlockByNumber`.
|
|
67
|
+
:param params: The RPC method parameters, e.g. `["0x1337", False]`.
|
|
68
|
+
|
|
69
|
+
:return: None
|
|
70
|
+
"""
|
|
71
|
+
await self._manager.send(method, params)
|
|
72
|
+
|
|
73
|
+
async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
|
|
74
|
+
"""
|
|
75
|
+
Receive the next unprocessed response for a request from the persistent
|
|
76
|
+
connection.
|
|
77
|
+
|
|
78
|
+
:return: The next unprocessed response for a request from the persistent
|
|
79
|
+
connection.
|
|
80
|
+
:rtype: Union[RPCResponse, FormattedEthSubscriptionResponse]
|
|
81
|
+
"""
|
|
82
|
+
return await self._manager.recv()
|
|
40
83
|
|
|
41
84
|
def process_subscriptions(self) -> "_AsyncPersistentMessageStream":
|
|
85
|
+
"""
|
|
86
|
+
Asynchronous iterator that yields messages from the subscription message stream.
|
|
87
|
+
|
|
88
|
+
:return: The subscription message stream.
|
|
89
|
+
:rtype: _AsyncPersistentMessageStream
|
|
90
|
+
"""
|
|
42
91
|
return self._manager._persistent_message_stream()
|