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
web3/manager.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing import (
|
|
|
5
5
|
Any,
|
|
6
6
|
AsyncGenerator,
|
|
7
7
|
Callable,
|
|
8
|
+
Coroutine,
|
|
9
|
+
Dict,
|
|
8
10
|
List,
|
|
9
11
|
Optional,
|
|
10
12
|
Sequence,
|
|
@@ -19,10 +21,10 @@ from eth_utils.toolz import (
|
|
|
19
21
|
from hexbytes import (
|
|
20
22
|
HexBytes,
|
|
21
23
|
)
|
|
22
|
-
from websockets.exceptions import (
|
|
23
|
-
ConnectionClosedOK,
|
|
24
|
-
)
|
|
25
24
|
|
|
25
|
+
from web3._utils.batching import (
|
|
26
|
+
RequestBatcher,
|
|
27
|
+
)
|
|
26
28
|
from web3._utils.caching import (
|
|
27
29
|
generate_cache_key,
|
|
28
30
|
)
|
|
@@ -36,6 +38,14 @@ from web3.exceptions import (
|
|
|
36
38
|
BadResponseFormat,
|
|
37
39
|
MethodUnavailable,
|
|
38
40
|
ProviderConnectionError,
|
|
41
|
+
RequestTimedOut,
|
|
42
|
+
TaskNotRunning,
|
|
43
|
+
TransactionNotFound,
|
|
44
|
+
Web3RPCError,
|
|
45
|
+
Web3TypeError,
|
|
46
|
+
)
|
|
47
|
+
from web3.method import (
|
|
48
|
+
Method,
|
|
39
49
|
)
|
|
40
50
|
from web3.middleware import (
|
|
41
51
|
AttributeDictMiddleware,
|
|
@@ -53,10 +63,16 @@ from web3.module import (
|
|
|
53
63
|
)
|
|
54
64
|
from web3.providers import (
|
|
55
65
|
AutoProvider,
|
|
66
|
+
JSONBaseProvider,
|
|
56
67
|
PersistentConnectionProvider,
|
|
57
68
|
)
|
|
69
|
+
from web3.providers.async_base import (
|
|
70
|
+
AsyncJSONBaseProvider,
|
|
71
|
+
)
|
|
58
72
|
from web3.types import (
|
|
73
|
+
FormattedEthSubscriptionResponse,
|
|
59
74
|
RPCEndpoint,
|
|
75
|
+
RPCRequest,
|
|
60
76
|
RPCResponse,
|
|
61
77
|
)
|
|
62
78
|
|
|
@@ -78,6 +94,13 @@ if TYPE_CHECKING:
|
|
|
78
94
|
|
|
79
95
|
|
|
80
96
|
NULL_RESPONSES = [None, HexBytes("0x"), "0x"]
|
|
97
|
+
KNOWN_REQUEST_TIMEOUT_MESSAGING = {
|
|
98
|
+
# Note: It's important to be very explicit here and not too broad. We don't want
|
|
99
|
+
# to accidentally catch a message that is not for a request timeout. In the worst
|
|
100
|
+
# case, we raise something more generic like `Web3RPCError`. JSON-RPC unfortunately
|
|
101
|
+
# has not standardized error codes for request timeouts.
|
|
102
|
+
"request timed out", # go-ethereum
|
|
103
|
+
}
|
|
81
104
|
METHOD_NOT_FOUND = -32601
|
|
82
105
|
|
|
83
106
|
|
|
@@ -86,6 +109,7 @@ def _raise_bad_response_format(response: RPCResponse, error: str = "") -> None:
|
|
|
86
109
|
raw_response = f"The raw response is: {response}"
|
|
87
110
|
|
|
88
111
|
if error is not None and error != "":
|
|
112
|
+
error = error[:-1] if error.endswith(".") else error
|
|
89
113
|
message = f"{message} {error}. {raw_response}"
|
|
90
114
|
else:
|
|
91
115
|
message = f"{message} {raw_response}"
|
|
@@ -116,6 +140,132 @@ def apply_null_result_formatters(
|
|
|
116
140
|
return response
|
|
117
141
|
|
|
118
142
|
|
|
143
|
+
def _validate_subscription_fields(response: RPCResponse) -> None:
|
|
144
|
+
params = response["params"]
|
|
145
|
+
subscription = params["subscription"]
|
|
146
|
+
if not isinstance(subscription, str) and not len(subscription) == 34:
|
|
147
|
+
_raise_bad_response_format(
|
|
148
|
+
response, "eth_subscription 'params' must include a 'subscription' field."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _validate_response(
|
|
153
|
+
response: RPCResponse,
|
|
154
|
+
error_formatters: Optional[Callable[..., Any]],
|
|
155
|
+
is_subscription_response: bool = False,
|
|
156
|
+
logger: Optional[logging.Logger] = None,
|
|
157
|
+
params: Optional[Any] = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
if "jsonrpc" not in response or response["jsonrpc"] != "2.0":
|
|
160
|
+
_raise_bad_response_format(
|
|
161
|
+
response, 'The "jsonrpc" field must be present with a value of "2.0".'
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
response_id = response.get("id")
|
|
165
|
+
if "id" in response:
|
|
166
|
+
int_error_msg = (
|
|
167
|
+
'"id" must be an integer or a string representation of an integer.'
|
|
168
|
+
)
|
|
169
|
+
if response_id is None and "error" in response:
|
|
170
|
+
# errors can sometimes have null `id`, according to the JSON-RPC spec
|
|
171
|
+
pass
|
|
172
|
+
elif not isinstance(response_id, (str, int)):
|
|
173
|
+
_raise_bad_response_format(response, int_error_msg)
|
|
174
|
+
elif isinstance(response_id, str):
|
|
175
|
+
try:
|
|
176
|
+
int(response_id)
|
|
177
|
+
except ValueError:
|
|
178
|
+
_raise_bad_response_format(response, int_error_msg)
|
|
179
|
+
elif is_subscription_response:
|
|
180
|
+
# if `id` is not present, this must be a subscription response
|
|
181
|
+
_validate_subscription_fields(response)
|
|
182
|
+
else:
|
|
183
|
+
_raise_bad_response_format(
|
|
184
|
+
response,
|
|
185
|
+
'Response must include an "id" field or be formatted as an '
|
|
186
|
+
"`eth_subscription` response.",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if all(key in response for key in {"error", "result"}):
|
|
190
|
+
_raise_bad_response_format(
|
|
191
|
+
response, 'Response cannot include both "error" and "result".'
|
|
192
|
+
)
|
|
193
|
+
elif (
|
|
194
|
+
not any(key in response for key in {"error", "result"})
|
|
195
|
+
and not is_subscription_response
|
|
196
|
+
):
|
|
197
|
+
_raise_bad_response_format(
|
|
198
|
+
response, 'Response must include either "error" or "result".'
|
|
199
|
+
)
|
|
200
|
+
elif "error" in response:
|
|
201
|
+
web3_rpc_error: Optional[Web3RPCError] = None
|
|
202
|
+
error = response["error"]
|
|
203
|
+
|
|
204
|
+
# raise the error when the value is a string
|
|
205
|
+
if error is None or not isinstance(error, dict):
|
|
206
|
+
_raise_bad_response_format(
|
|
207
|
+
response,
|
|
208
|
+
'response["error"] must be a valid object as defined by the '
|
|
209
|
+
"JSON-RPC 2.0 specification.",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# errors must include a message
|
|
213
|
+
error_message = error.get("message")
|
|
214
|
+
if not isinstance(error_message, str):
|
|
215
|
+
_raise_bad_response_format(
|
|
216
|
+
response, 'error["message"] is required and must be a string value.'
|
|
217
|
+
)
|
|
218
|
+
elif error_message == "transaction not found":
|
|
219
|
+
transaction_hash = params[0]
|
|
220
|
+
web3_rpc_error = TransactionNotFound(
|
|
221
|
+
repr(error),
|
|
222
|
+
rpc_response=response,
|
|
223
|
+
user_message=(f"Transaction with hash {transaction_hash!r} not found."),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# errors must include an integer code
|
|
227
|
+
code = error.get("code")
|
|
228
|
+
if not isinstance(code, int):
|
|
229
|
+
_raise_bad_response_format(
|
|
230
|
+
response, 'error["code"] is required and must be an integer value.'
|
|
231
|
+
)
|
|
232
|
+
elif code == METHOD_NOT_FOUND:
|
|
233
|
+
web3_rpc_error = MethodUnavailable(
|
|
234
|
+
repr(error),
|
|
235
|
+
rpc_response=response,
|
|
236
|
+
user_message=(
|
|
237
|
+
"This method is not available. Check your node provider or your "
|
|
238
|
+
"client's API docs to see what methods are supported and / or "
|
|
239
|
+
"currently enabled."
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
elif any(
|
|
243
|
+
# parse specific timeout messages
|
|
244
|
+
timeout_str in error_message.lower()
|
|
245
|
+
for timeout_str in KNOWN_REQUEST_TIMEOUT_MESSAGING
|
|
246
|
+
):
|
|
247
|
+
web3_rpc_error = RequestTimedOut(
|
|
248
|
+
repr(error),
|
|
249
|
+
rpc_response=response,
|
|
250
|
+
user_message=(
|
|
251
|
+
"The request timed out. Check the connection to your node and "
|
|
252
|
+
"try again."
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if web3_rpc_error is None:
|
|
257
|
+
# if no condition was met above, raise a more generic `Web3RPCError`
|
|
258
|
+
web3_rpc_error = Web3RPCError(repr(error), rpc_response=response)
|
|
259
|
+
|
|
260
|
+
response = apply_error_formatters(error_formatters, response)
|
|
261
|
+
logger.debug(f"RPC error response: {response}")
|
|
262
|
+
|
|
263
|
+
raise web3_rpc_error
|
|
264
|
+
|
|
265
|
+
elif "result" not in response and not is_subscription_response:
|
|
266
|
+
_raise_bad_response_format(response)
|
|
267
|
+
|
|
268
|
+
|
|
119
269
|
class RequestManager:
|
|
120
270
|
logger = logging.getLogger("web3.manager.RequestManager")
|
|
121
271
|
|
|
@@ -125,7 +275,7 @@ class RequestManager:
|
|
|
125
275
|
self,
|
|
126
276
|
w3: Union["AsyncWeb3", "Web3"],
|
|
127
277
|
provider: Optional[Union["BaseProvider", "AsyncBaseProvider"]] = None,
|
|
128
|
-
|
|
278
|
+
middleware: Optional[Sequence[Tuple[Middleware, str]]] = None,
|
|
129
279
|
) -> None:
|
|
130
280
|
self.w3 = w3
|
|
131
281
|
|
|
@@ -134,10 +284,10 @@ class RequestManager:
|
|
|
134
284
|
else:
|
|
135
285
|
self.provider = provider
|
|
136
286
|
|
|
137
|
-
if
|
|
138
|
-
|
|
287
|
+
if middleware is None:
|
|
288
|
+
middleware = self.get_default_middleware()
|
|
139
289
|
|
|
140
|
-
self.middleware_onion = NamedElementOnion(
|
|
290
|
+
self.middleware_onion = NamedElementOnion(middleware)
|
|
141
291
|
|
|
142
292
|
if isinstance(provider, PersistentConnectionProvider):
|
|
143
293
|
# set up the request processor to be able to properly process ordered
|
|
@@ -145,9 +295,6 @@ class RequestManager:
|
|
|
145
295
|
provider = cast(PersistentConnectionProvider, self.provider)
|
|
146
296
|
self._request_processor: RequestProcessor = provider._request_processor
|
|
147
297
|
|
|
148
|
-
w3: Union["AsyncWeb3", "Web3"] = None
|
|
149
|
-
_provider = None
|
|
150
|
-
|
|
151
298
|
@property
|
|
152
299
|
def provider(self) -> Union["BaseProvider", "AsyncBaseProvider"]:
|
|
153
300
|
return self._provider
|
|
@@ -157,9 +304,9 @@ class RequestManager:
|
|
|
157
304
|
self._provider = provider
|
|
158
305
|
|
|
159
306
|
@staticmethod
|
|
160
|
-
def
|
|
307
|
+
def get_default_middleware() -> List[Tuple[Middleware, str]]:
|
|
161
308
|
"""
|
|
162
|
-
List the default
|
|
309
|
+
List the default middleware for the request manager.
|
|
163
310
|
Documentation should remain in sync with these defaults.
|
|
164
311
|
"""
|
|
165
312
|
return [
|
|
@@ -202,86 +349,43 @@ class RequestManager:
|
|
|
202
349
|
#
|
|
203
350
|
# See also: https://www.jsonrpc.org/specification
|
|
204
351
|
#
|
|
205
|
-
@staticmethod
|
|
206
352
|
def formatted_response(
|
|
353
|
+
self,
|
|
207
354
|
response: RPCResponse,
|
|
208
355
|
params: Any,
|
|
209
356
|
error_formatters: Optional[Callable[..., Any]] = None,
|
|
210
357
|
null_result_formatters: Optional[Callable[..., Any]] = None,
|
|
211
358
|
) -> Any:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
# id is not enforced (as per the spec) but if present, it must be a
|
|
219
|
-
# string or integer
|
|
220
|
-
# TODO: v7 - enforce id per the spec
|
|
221
|
-
if "id" in response:
|
|
222
|
-
response_id = response["id"]
|
|
223
|
-
# id is always None for errors
|
|
224
|
-
if response_id is None and "error" not in response:
|
|
225
|
-
_raise_bad_response_format(
|
|
226
|
-
response, '"id" must be None when an error is present'
|
|
227
|
-
)
|
|
228
|
-
elif not isinstance(response_id, (str, int, type(None))):
|
|
229
|
-
_raise_bad_response_format(response, '"id" must be a string or integer')
|
|
230
|
-
|
|
231
|
-
# Response may not include both "error" and "result"
|
|
232
|
-
if "error" in response and "result" in response:
|
|
233
|
-
_raise_bad_response_format(
|
|
234
|
-
response, 'Response cannot include both "error" and "result"'
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# Format and validate errors
|
|
238
|
-
elif "error" in response:
|
|
239
|
-
error = response.get("error")
|
|
240
|
-
# Raise the error when the value is a string
|
|
241
|
-
if error is None or isinstance(error, str):
|
|
242
|
-
raise ValueError(error)
|
|
243
|
-
|
|
244
|
-
# Errors must include an integer code
|
|
245
|
-
code = error.get("code")
|
|
246
|
-
if not isinstance(code, int):
|
|
247
|
-
_raise_bad_response_format(response, "error['code'] must be an integer")
|
|
248
|
-
elif code == METHOD_NOT_FOUND:
|
|
249
|
-
raise MethodUnavailable(error)
|
|
250
|
-
|
|
251
|
-
# Errors must include a message
|
|
252
|
-
if not isinstance(error.get("message"), str):
|
|
253
|
-
_raise_bad_response_format(
|
|
254
|
-
response, "error['message'] must be a string"
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
apply_error_formatters(error_formatters, response)
|
|
359
|
+
is_subscription_response = (
|
|
360
|
+
response.get("method") == "eth_subscription"
|
|
361
|
+
and response.get("params") is not None
|
|
362
|
+
and response["params"].get("subscription") is not None
|
|
363
|
+
and response["params"].get("result") is not None
|
|
364
|
+
)
|
|
258
365
|
|
|
259
|
-
|
|
366
|
+
_validate_response(
|
|
367
|
+
response,
|
|
368
|
+
error_formatters,
|
|
369
|
+
is_subscription_response=is_subscription_response,
|
|
370
|
+
logger=self.logger,
|
|
371
|
+
params=params,
|
|
372
|
+
)
|
|
260
373
|
|
|
261
|
-
#
|
|
262
|
-
|
|
374
|
+
# format results
|
|
375
|
+
if "result" in response:
|
|
263
376
|
# Null values for result should apply null_result_formatters
|
|
264
377
|
# Skip when result not present in the response (fallback to False)
|
|
265
378
|
if response.get("result", False) in NULL_RESPONSES:
|
|
266
379
|
apply_null_result_formatters(null_result_formatters, response, params)
|
|
267
380
|
return response.get("result")
|
|
268
381
|
|
|
269
|
-
#
|
|
270
|
-
elif
|
|
271
|
-
response.get("method") == "eth_subscription"
|
|
272
|
-
and response.get("params") is not None
|
|
273
|
-
and response["params"].get("subscription") is not None
|
|
274
|
-
and response["params"].get("result") is not None
|
|
275
|
-
):
|
|
382
|
+
# response from eth_subscription includes response["params"]["result"]
|
|
383
|
+
elif is_subscription_response:
|
|
276
384
|
return {
|
|
277
385
|
"subscription": response["params"]["subscription"],
|
|
278
386
|
"result": response["params"]["result"],
|
|
279
387
|
}
|
|
280
388
|
|
|
281
|
-
# Any other response type raises BadResponseFormat
|
|
282
|
-
else:
|
|
283
|
-
_raise_bad_response_format(response)
|
|
284
|
-
|
|
285
389
|
def request_blocking(
|
|
286
390
|
self,
|
|
287
391
|
method: Union[RPCEndpoint, Callable[..., RPCEndpoint]],
|
|
@@ -312,32 +416,181 @@ class RequestManager:
|
|
|
312
416
|
response, params, error_formatters, null_result_formatters
|
|
313
417
|
)
|
|
314
418
|
|
|
419
|
+
# -- batch requests management -- #
|
|
420
|
+
|
|
421
|
+
def _batch_requests(self) -> RequestBatcher[Method[Callable[..., Any]]]:
|
|
422
|
+
"""
|
|
423
|
+
Context manager for making batch requests
|
|
424
|
+
"""
|
|
425
|
+
if not isinstance(self.provider, (AsyncJSONBaseProvider, JSONBaseProvider)):
|
|
426
|
+
raise Web3TypeError("Batch requests are not supported by this provider.")
|
|
427
|
+
return RequestBatcher(self.w3)
|
|
428
|
+
|
|
429
|
+
def _make_batch_request(
|
|
430
|
+
self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
|
|
431
|
+
) -> List[RPCResponse]:
|
|
432
|
+
"""
|
|
433
|
+
Make a batch request using the provider
|
|
434
|
+
"""
|
|
435
|
+
provider = cast(JSONBaseProvider, self.provider)
|
|
436
|
+
request_func = provider.batch_request_func(
|
|
437
|
+
cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
438
|
+
)
|
|
439
|
+
responses = request_func(
|
|
440
|
+
[
|
|
441
|
+
(method, params)
|
|
442
|
+
for (method, params), _response_formatters in requests_info
|
|
443
|
+
]
|
|
444
|
+
)
|
|
445
|
+
formatted_responses = [
|
|
446
|
+
self._format_batched_response(info, resp)
|
|
447
|
+
for info, resp in zip(requests_info, responses)
|
|
448
|
+
]
|
|
449
|
+
return list(formatted_responses)
|
|
450
|
+
|
|
451
|
+
async def _async_make_batch_request(
|
|
452
|
+
self,
|
|
453
|
+
requests_info: List[
|
|
454
|
+
Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
|
|
455
|
+
],
|
|
456
|
+
) -> List[RPCResponse]:
|
|
457
|
+
"""
|
|
458
|
+
Make an asynchronous batch request using the provider
|
|
459
|
+
"""
|
|
460
|
+
provider = cast(AsyncJSONBaseProvider, self.provider)
|
|
461
|
+
request_func = await provider.batch_request_func(
|
|
462
|
+
cast("AsyncWeb3", self.w3),
|
|
463
|
+
cast("MiddlewareOnion", self.middleware_onion),
|
|
464
|
+
)
|
|
465
|
+
# since we add items to the batch without awaiting, we unpack the coroutines
|
|
466
|
+
# and await them all here
|
|
467
|
+
unpacked_requests_info = await asyncio.gather(*requests_info)
|
|
468
|
+
responses = await request_func(
|
|
469
|
+
[
|
|
470
|
+
(method, params)
|
|
471
|
+
for (method, params), _response_formatters in unpacked_requests_info
|
|
472
|
+
]
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if isinstance(self.provider, PersistentConnectionProvider):
|
|
476
|
+
# call _process_response for each response in the batch
|
|
477
|
+
return [
|
|
478
|
+
cast(RPCResponse, await self._process_response(resp))
|
|
479
|
+
for resp in responses
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
formatted_responses = [
|
|
483
|
+
self._format_batched_response(info, resp)
|
|
484
|
+
for info, resp in zip(unpacked_requests_info, responses)
|
|
485
|
+
]
|
|
486
|
+
return list(formatted_responses)
|
|
487
|
+
|
|
488
|
+
def _format_batched_response(
|
|
489
|
+
self,
|
|
490
|
+
requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
|
|
491
|
+
response: RPCResponse,
|
|
492
|
+
) -> RPCResponse:
|
|
493
|
+
result_formatters, error_formatters, null_result_formatters = requests_info[1]
|
|
494
|
+
return apply_result_formatters(
|
|
495
|
+
result_formatters,
|
|
496
|
+
self.formatted_response(
|
|
497
|
+
response,
|
|
498
|
+
requests_info[0][1],
|
|
499
|
+
error_formatters,
|
|
500
|
+
null_result_formatters,
|
|
501
|
+
),
|
|
502
|
+
)
|
|
503
|
+
|
|
315
504
|
# -- persistent connection -- #
|
|
316
505
|
|
|
317
|
-
async def
|
|
506
|
+
async def socket_request(
|
|
507
|
+
self,
|
|
508
|
+
method: RPCEndpoint,
|
|
509
|
+
params: Any,
|
|
510
|
+
response_formatters: Optional[
|
|
511
|
+
Tuple[Dict[str, Callable[..., Any]], Callable[..., Any], Callable[..., Any]]
|
|
512
|
+
] = None,
|
|
513
|
+
) -> RPCResponse:
|
|
318
514
|
provider = cast(PersistentConnectionProvider, self._provider)
|
|
319
|
-
|
|
320
|
-
|
|
515
|
+
self.logger.debug(
|
|
516
|
+
"Making request to open socket connection and waiting for response: "
|
|
517
|
+
f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
|
|
518
|
+
f" params: {params}"
|
|
519
|
+
)
|
|
520
|
+
rpc_request = await self.send(method, params)
|
|
521
|
+
provider._request_processor.cache_request_information(
|
|
522
|
+
rpc_request["id"],
|
|
523
|
+
rpc_request["method"],
|
|
524
|
+
rpc_request["params"],
|
|
525
|
+
response_formatters=response_formatters or ((), (), ()),
|
|
526
|
+
)
|
|
527
|
+
return await self.recv_for_request(rpc_request)
|
|
528
|
+
|
|
529
|
+
async def send(self, method: RPCEndpoint, params: Any) -> RPCRequest:
|
|
530
|
+
provider = cast(PersistentConnectionProvider, self._provider)
|
|
531
|
+
async_w3 = cast("AsyncWeb3", self.w3)
|
|
532
|
+
middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
|
|
533
|
+
send_func = await provider.send_func(
|
|
534
|
+
async_w3,
|
|
535
|
+
middleware_onion,
|
|
321
536
|
)
|
|
322
537
|
self.logger.debug(
|
|
323
|
-
"
|
|
324
|
-
f"
|
|
538
|
+
"Sending request to open socket connection: "
|
|
539
|
+
f"{provider.get_endpoint_uri_or_ipc_path()},\n method: {method},\n"
|
|
540
|
+
f" params: {params}"
|
|
541
|
+
)
|
|
542
|
+
return await send_func(method, params)
|
|
543
|
+
|
|
544
|
+
async def recv_for_request(self, rpc_request: RPCRequest) -> RPCResponse:
|
|
545
|
+
provider = cast(PersistentConnectionProvider, self._provider)
|
|
546
|
+
async_w3 = cast("AsyncWeb3", self.w3)
|
|
547
|
+
middleware_onion = cast("MiddlewareOnion", self.middleware_onion)
|
|
548
|
+
recv_func = await provider.recv_func(
|
|
549
|
+
async_w3,
|
|
550
|
+
middleware_onion,
|
|
551
|
+
)
|
|
552
|
+
self.logger.debug(
|
|
553
|
+
"Getting response for request from open socket connection:\n"
|
|
554
|
+
f" request: {rpc_request}"
|
|
555
|
+
)
|
|
556
|
+
response = await recv_func(rpc_request)
|
|
557
|
+
try:
|
|
558
|
+
return cast(RPCResponse, await self._process_response(response))
|
|
559
|
+
except Exception:
|
|
560
|
+
response_id_key = generate_cache_key(response["id"])
|
|
561
|
+
provider._request_processor._request_information_cache.pop(response_id_key)
|
|
562
|
+
raise
|
|
563
|
+
|
|
564
|
+
async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
|
|
565
|
+
provider = cast(PersistentConnectionProvider, self._provider)
|
|
566
|
+
self.logger.debug(
|
|
567
|
+
"Getting next response from open socket connection: "
|
|
568
|
+
f"{provider.get_endpoint_uri_or_ipc_path()}"
|
|
569
|
+
)
|
|
570
|
+
# pop from the queue since the listener task is responsible for reading
|
|
571
|
+
# directly from the socket
|
|
572
|
+
request_response_cache = self._request_processor._request_response_cache
|
|
573
|
+
_key, response = await request_response_cache.async_await_and_popitem(
|
|
574
|
+
last=False,
|
|
575
|
+
timeout=provider.request_timeout,
|
|
325
576
|
)
|
|
326
|
-
response = await request_func(method, params)
|
|
327
577
|
return await self._process_response(response)
|
|
328
578
|
|
|
329
579
|
def _persistent_message_stream(self) -> "_AsyncPersistentMessageStream":
|
|
330
580
|
return _AsyncPersistentMessageStream(self)
|
|
331
581
|
|
|
332
|
-
async def _get_next_message(self) ->
|
|
582
|
+
async def _get_next_message(self) -> FormattedEthSubscriptionResponse:
|
|
333
583
|
return await self._message_stream().__anext__()
|
|
334
584
|
|
|
335
|
-
async def _message_stream(
|
|
585
|
+
async def _message_stream(
|
|
586
|
+
self,
|
|
587
|
+
) -> AsyncGenerator[FormattedEthSubscriptionResponse, None]:
|
|
336
588
|
if not isinstance(self._provider, PersistentConnectionProvider):
|
|
337
|
-
raise
|
|
589
|
+
raise Web3TypeError(
|
|
338
590
|
"Only providers that maintain an open, persistent connection "
|
|
339
591
|
"can listen to streams."
|
|
340
592
|
)
|
|
593
|
+
async_w3 = cast("AsyncWeb3", self.w3)
|
|
341
594
|
|
|
342
595
|
if self._provider._message_listener_task is None:
|
|
343
596
|
raise ProviderConnectionError(
|
|
@@ -345,20 +598,37 @@ class RequestManager:
|
|
|
345
598
|
)
|
|
346
599
|
|
|
347
600
|
while True:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
601
|
+
try:
|
|
602
|
+
response = await self._request_processor.pop_raw_response(
|
|
603
|
+
subscription=True
|
|
604
|
+
)
|
|
605
|
+
# if the subscription was unsubscribed from, we won't have a formatted
|
|
606
|
+
# response because we lost the request information.
|
|
607
|
+
sub_id = response.get(
|
|
608
|
+
"subscription", response.get("params", {}).get("subscription")
|
|
609
|
+
)
|
|
610
|
+
if async_w3.subscription_manager.get_by_id(sub_id):
|
|
611
|
+
# if active subscription, process and yield the formatted response
|
|
612
|
+
formatted_sub_response = cast(
|
|
613
|
+
FormattedEthSubscriptionResponse,
|
|
614
|
+
await self._process_response(response),
|
|
615
|
+
)
|
|
616
|
+
yield formatted_sub_response
|
|
617
|
+
else:
|
|
618
|
+
# if not an active sub, skip processing and continue
|
|
619
|
+
continue
|
|
620
|
+
except TaskNotRunning:
|
|
621
|
+
await asyncio.sleep(0)
|
|
622
|
+
self._provider._handle_listener_task_exceptions()
|
|
623
|
+
self.logger.error(
|
|
624
|
+
"Message listener background task has stopped unexpectedly. "
|
|
625
|
+
"Stopping message stream."
|
|
626
|
+
)
|
|
627
|
+
return
|
|
628
|
+
|
|
629
|
+
async def _process_response(
|
|
630
|
+
self, response: RPCResponse
|
|
631
|
+
) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
|
|
362
632
|
provider = cast(PersistentConnectionProvider, self._provider)
|
|
363
633
|
request_info = self._request_processor.get_request_information_for_response(
|
|
364
634
|
response
|
|
@@ -421,8 +691,5 @@ class _AsyncPersistentMessageStream:
|
|
|
421
691
|
def __aiter__(self) -> Self:
|
|
422
692
|
return self
|
|
423
693
|
|
|
424
|
-
async def __anext__(self) ->
|
|
425
|
-
|
|
426
|
-
return await self.manager._get_next_message()
|
|
427
|
-
except ConnectionClosedOK:
|
|
428
|
-
raise StopAsyncIteration
|
|
694
|
+
async def __anext__(self) -> FormattedEthSubscriptionResponse:
|
|
695
|
+
return await self.manager._get_next_message()
|