web3 7.7.0__py3-none-any.whl → 7.9.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/async_ens.py +1 -1
- ens/ens.py +1 -1
- web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +3 -3
- 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 +7 -7
- 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/error_formatters_utils.py +1 -1
- web3/_utils/events.py +1 -1
- web3/_utils/formatters.py +28 -0
- web3/_utils/http_session_manager.py +19 -1
- web3/_utils/method_formatters.py +121 -20
- web3/_utils/module_testing/eth_module.py +102 -2
- web3/_utils/module_testing/module_testing_utils.py +0 -42
- web3/_utils/module_testing/persistent_connection_provider.py +39 -1
- web3/_utils/rpc_abi.py +1 -0
- web3/_utils/validation.py +191 -0
- web3/beacon/api_endpoints.py +10 -0
- web3/beacon/async_beacon.py +47 -0
- web3/beacon/beacon.py +45 -0
- web3/contract/async_contract.py +2 -206
- web3/contract/base_contract.py +208 -12
- web3/contract/contract.py +2 -205
- web3/datastructures.py +4 -0
- web3/eth/async_eth.py +19 -0
- web3/eth/eth.py +15 -0
- web3/gas_strategies/time_based.py +1 -1
- web3/manager.py +47 -194
- web3/middleware/base.py +14 -6
- web3/providers/async_base.py +5 -4
- web3/providers/base.py +3 -3
- web3/providers/persistent/persistent.py +29 -3
- web3/providers/persistent/request_processor.py +11 -1
- web3/providers/persistent/subscription_manager.py +116 -46
- web3/providers/persistent/websocket.py +8 -3
- web3/providers/rpc/async_rpc.py +8 -3
- web3/providers/rpc/rpc.py +8 -3
- web3/types.py +36 -13
- web3/utils/subscriptions.py +5 -1
- {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/LICENSE +1 -1
- {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/METADATA +11 -7
- {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/RECORD +61 -61
- {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/WHEEL +1 -1
- {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/top_level.txt +0 -0
web3/contract/contract.py
CHANGED
|
@@ -13,7 +13,6 @@ from typing import (
|
|
|
13
13
|
|
|
14
14
|
from eth_typing import (
|
|
15
15
|
ABI,
|
|
16
|
-
ABIFunction,
|
|
17
16
|
ChecksumAddress,
|
|
18
17
|
)
|
|
19
18
|
from eth_utils import (
|
|
@@ -31,7 +30,6 @@ from hexbytes import (
|
|
|
31
30
|
|
|
32
31
|
from web3._utils.abi import (
|
|
33
32
|
fallback_func_abi_exists,
|
|
34
|
-
get_name_from_abi_element_identifier,
|
|
35
33
|
receive_func_abi_exists,
|
|
36
34
|
)
|
|
37
35
|
from web3._utils.abi_element_identifiers import (
|
|
@@ -42,8 +40,6 @@ from web3._utils.compat import (
|
|
|
42
40
|
Self,
|
|
43
41
|
)
|
|
44
42
|
from web3._utils.contracts import (
|
|
45
|
-
copy_contract_event,
|
|
46
|
-
copy_contract_function,
|
|
47
43
|
parse_block_identifier,
|
|
48
44
|
)
|
|
49
45
|
from web3._utils.datatypes import (
|
|
@@ -86,12 +82,6 @@ from web3.contract.utils import (
|
|
|
86
82
|
transact_with_contract_function,
|
|
87
83
|
)
|
|
88
84
|
from web3.exceptions import (
|
|
89
|
-
ABIEventNotFound,
|
|
90
|
-
ABIFunctionNotFound,
|
|
91
|
-
MismatchedABI,
|
|
92
|
-
NoABIEventsFound,
|
|
93
|
-
NoABIFound,
|
|
94
|
-
NoABIFunctionsFound,
|
|
95
85
|
Web3AttributeError,
|
|
96
86
|
Web3TypeError,
|
|
97
87
|
Web3ValidationError,
|
|
@@ -103,12 +93,6 @@ from web3.types import (
|
|
|
103
93
|
StateOverride,
|
|
104
94
|
TxParams,
|
|
105
95
|
)
|
|
106
|
-
from web3.utils.abi import (
|
|
107
|
-
_filter_by_argument_count,
|
|
108
|
-
_get_any_abi_signature_with_name,
|
|
109
|
-
_mismatched_abi_error_diagnosis,
|
|
110
|
-
get_abi_element,
|
|
111
|
-
)
|
|
112
96
|
|
|
113
97
|
if TYPE_CHECKING:
|
|
114
98
|
from ens import ENS # noqa: F401
|
|
@@ -119,9 +103,6 @@ class ContractEvent(BaseContractEvent):
|
|
|
119
103
|
# mypy types
|
|
120
104
|
w3: "Web3"
|
|
121
105
|
|
|
122
|
-
def __call__(self, *args: Any, **kwargs: Any) -> "ContractEvent":
|
|
123
|
-
return copy_contract_event(self, *args, **kwargs)
|
|
124
|
-
|
|
125
106
|
@combomethod
|
|
126
107
|
def get_logs(
|
|
127
108
|
self,
|
|
@@ -255,162 +236,18 @@ class ContractEvent(BaseContractEvent):
|
|
|
255
236
|
builder.address = self.address
|
|
256
237
|
return builder
|
|
257
238
|
|
|
258
|
-
@classmethod
|
|
259
|
-
def factory(cls, class_name: str, **kwargs: Any) -> Self:
|
|
260
|
-
return PropertyCheckingFactory(class_name, (cls,), kwargs)()
|
|
261
239
|
|
|
262
|
-
|
|
263
|
-
class ContractEvents(BaseContractEvents):
|
|
240
|
+
class ContractEvents(BaseContractEvents[ContractEvent]):
|
|
264
241
|
def __init__(
|
|
265
242
|
self, abi: ABI, w3: "Web3", address: Optional[ChecksumAddress] = None
|
|
266
243
|
) -> None:
|
|
267
244
|
super().__init__(abi, w3, ContractEvent, address)
|
|
268
245
|
|
|
269
|
-
def __getattr__(self, event_name: str) -> "ContractEvent":
|
|
270
|
-
if super().__getattribute__("abi") is None:
|
|
271
|
-
raise NoABIFound(
|
|
272
|
-
"There is no ABI found for this contract.",
|
|
273
|
-
)
|
|
274
|
-
elif "_events" not in self.__dict__ or len(self._events) == 0:
|
|
275
|
-
raise NoABIEventsFound(
|
|
276
|
-
"The abi for this contract contains no event definitions. ",
|
|
277
|
-
"Are you sure you provided the correct contract abi?",
|
|
278
|
-
)
|
|
279
|
-
elif get_name_from_abi_element_identifier(event_name) not in [
|
|
280
|
-
get_name_from_abi_element_identifier(event["name"])
|
|
281
|
-
for event in self._events
|
|
282
|
-
]:
|
|
283
|
-
raise ABIEventNotFound(
|
|
284
|
-
f"The event '{event_name}' was not found in this contract's abi. ",
|
|
285
|
-
"Are you sure you provided the correct contract abi?",
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
if "(" not in event_name:
|
|
289
|
-
event_name = _get_any_abi_signature_with_name(event_name, self._events)
|
|
290
|
-
else:
|
|
291
|
-
event_name = f"_{event_name}"
|
|
292
|
-
|
|
293
|
-
return super().__getattribute__(event_name)
|
|
294
|
-
|
|
295
|
-
def __getitem__(self, event_name: str) -> "ContractEvent":
|
|
296
|
-
return getattr(self, event_name)
|
|
297
|
-
|
|
298
|
-
def __iter__(self) -> Iterable["ContractEvent"]:
|
|
299
|
-
if not hasattr(self, "_events") or not self._events:
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
for event in self._events:
|
|
303
|
-
yield self[abi_to_signature(event)]
|
|
304
|
-
|
|
305
246
|
|
|
306
247
|
class ContractFunction(BaseContractFunction):
|
|
307
248
|
# mypy types
|
|
308
249
|
w3: "Web3"
|
|
309
250
|
|
|
310
|
-
def __call__(self, *args: Any, **kwargs: Any) -> "ContractFunction":
|
|
311
|
-
# When a function is called, check arguments to obtain the correct function
|
|
312
|
-
# in the contract. self will be used if all args and kwargs are
|
|
313
|
-
# encodable to self.abi, otherwise the correct function is obtained from
|
|
314
|
-
# the contract.
|
|
315
|
-
if (
|
|
316
|
-
self.abi_element_identifier in [FallbackFn, ReceiveFn]
|
|
317
|
-
or self.abi_element_identifier == "constructor"
|
|
318
|
-
):
|
|
319
|
-
return copy_contract_function(self, *args, **kwargs)
|
|
320
|
-
|
|
321
|
-
all_functions = cast(
|
|
322
|
-
List[ABIFunction],
|
|
323
|
-
filter_abi_by_type(
|
|
324
|
-
"function",
|
|
325
|
-
self.contract_abi,
|
|
326
|
-
),
|
|
327
|
-
)
|
|
328
|
-
# Filter functions by name to obtain function signatures
|
|
329
|
-
function_name = get_name_from_abi_element_identifier(
|
|
330
|
-
self.abi_element_identifier
|
|
331
|
-
)
|
|
332
|
-
function_abis = [
|
|
333
|
-
function for function in all_functions if function["name"] == function_name
|
|
334
|
-
]
|
|
335
|
-
num_args = len(args) + len(kwargs)
|
|
336
|
-
function_abis_with_arg_count = cast(
|
|
337
|
-
List[ABIFunction],
|
|
338
|
-
_filter_by_argument_count(
|
|
339
|
-
num_args,
|
|
340
|
-
function_abis,
|
|
341
|
-
),
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
if not len(function_abis_with_arg_count):
|
|
345
|
-
# Build an ABI without arguments to determine if one exists
|
|
346
|
-
function_abis_with_arg_count = [
|
|
347
|
-
ABIFunction({"type": "function", "name": function_name})
|
|
348
|
-
]
|
|
349
|
-
|
|
350
|
-
# Check that arguments in call match a function ABI
|
|
351
|
-
num_attempts = 0
|
|
352
|
-
function_abi_matches = []
|
|
353
|
-
contract_function = None
|
|
354
|
-
for abi in function_abis_with_arg_count:
|
|
355
|
-
try:
|
|
356
|
-
num_attempts += 1
|
|
357
|
-
|
|
358
|
-
# Search for a function ABI that matches the arguments used
|
|
359
|
-
function_abi_matches.append(
|
|
360
|
-
cast(
|
|
361
|
-
ABIFunction,
|
|
362
|
-
get_abi_element(
|
|
363
|
-
function_abis,
|
|
364
|
-
abi_to_signature(abi),
|
|
365
|
-
*args,
|
|
366
|
-
abi_codec=self.w3.codec,
|
|
367
|
-
**kwargs,
|
|
368
|
-
),
|
|
369
|
-
)
|
|
370
|
-
)
|
|
371
|
-
except MismatchedABI:
|
|
372
|
-
# ignore exceptions
|
|
373
|
-
continue
|
|
374
|
-
|
|
375
|
-
if len(function_abi_matches) == 1:
|
|
376
|
-
function_abi = function_abi_matches[0]
|
|
377
|
-
if abi_to_signature(self.abi) == abi_to_signature(function_abi):
|
|
378
|
-
contract_function = self
|
|
379
|
-
else:
|
|
380
|
-
# Found a match that is not self
|
|
381
|
-
contract_function = ContractFunction.factory(
|
|
382
|
-
abi_to_signature(function_abi),
|
|
383
|
-
w3=self.w3,
|
|
384
|
-
contract_abi=self.contract_abi,
|
|
385
|
-
address=self.address,
|
|
386
|
-
abi_element_identifier=abi_to_signature(function_abi),
|
|
387
|
-
abi=function_abi,
|
|
388
|
-
)
|
|
389
|
-
else:
|
|
390
|
-
for abi in function_abi_matches:
|
|
391
|
-
if abi_to_signature(self.abi) == abi_to_signature(abi):
|
|
392
|
-
contract_function = self
|
|
393
|
-
break
|
|
394
|
-
else:
|
|
395
|
-
# Raise exception if multiple found
|
|
396
|
-
raise MismatchedABI(
|
|
397
|
-
_mismatched_abi_error_diagnosis(
|
|
398
|
-
function_name,
|
|
399
|
-
self.contract_abi,
|
|
400
|
-
len(function_abi_matches),
|
|
401
|
-
num_args,
|
|
402
|
-
*args,
|
|
403
|
-
abi_codec=self.w3.codec,
|
|
404
|
-
**kwargs,
|
|
405
|
-
)
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
return copy_contract_function(contract_function, *args, **kwargs)
|
|
409
|
-
|
|
410
|
-
@classmethod
|
|
411
|
-
def factory(cls, class_name: str, **kwargs: Any) -> Self:
|
|
412
|
-
return PropertyCheckingFactory(class_name, (cls,), kwargs)()
|
|
413
|
-
|
|
414
251
|
def call(
|
|
415
252
|
self,
|
|
416
253
|
transaction: Optional[TxParams] = None,
|
|
@@ -555,7 +392,7 @@ class ContractFunction(BaseContractFunction):
|
|
|
555
392
|
return cast(ContractFunction, NonExistentReceiveFunction())
|
|
556
393
|
|
|
557
394
|
|
|
558
|
-
class ContractFunctions(BaseContractFunctions):
|
|
395
|
+
class ContractFunctions(BaseContractFunctions[ContractFunction]):
|
|
559
396
|
def __init__(
|
|
560
397
|
self,
|
|
561
398
|
abi: ABI,
|
|
@@ -565,46 +402,6 @@ class ContractFunctions(BaseContractFunctions):
|
|
|
565
402
|
) -> None:
|
|
566
403
|
super().__init__(abi, w3, ContractFunction, address, decode_tuples)
|
|
567
404
|
|
|
568
|
-
def __iter__(self) -> Iterable["ContractFunction"]:
|
|
569
|
-
if not hasattr(self, "_functions") or not self._functions:
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
for func in self._functions:
|
|
573
|
-
yield self[abi_to_signature(func)]
|
|
574
|
-
|
|
575
|
-
def __getattr__(self, function_name: str) -> "ContractFunction":
|
|
576
|
-
if super().__getattribute__("abi") is None:
|
|
577
|
-
raise NoABIFound(
|
|
578
|
-
"There is no ABI found for this contract.",
|
|
579
|
-
)
|
|
580
|
-
elif "_functions" not in self.__dict__ or len(self._functions) == 0:
|
|
581
|
-
raise NoABIFunctionsFound(
|
|
582
|
-
"The abi for this contract contains no function definitions. ",
|
|
583
|
-
"Are you sure you provided the correct contract abi?",
|
|
584
|
-
)
|
|
585
|
-
elif get_name_from_abi_element_identifier(function_name) not in [
|
|
586
|
-
get_name_from_abi_element_identifier(function["name"])
|
|
587
|
-
for function in self._functions
|
|
588
|
-
]:
|
|
589
|
-
raise ABIFunctionNotFound(
|
|
590
|
-
f"The function '{function_name}' was not found in this ",
|
|
591
|
-
"contract's abi.",
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
if "(" not in function_name:
|
|
595
|
-
function_name = _get_any_abi_signature_with_name(
|
|
596
|
-
function_name, self._functions
|
|
597
|
-
)
|
|
598
|
-
else:
|
|
599
|
-
function_name = f"_{function_name}"
|
|
600
|
-
|
|
601
|
-
return super().__getattribute__(
|
|
602
|
-
function_name,
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
def __getitem__(self, function_name: str) -> "ContractFunction":
|
|
606
|
-
return getattr(self, function_name)
|
|
607
|
-
|
|
608
405
|
|
|
609
406
|
class Contract(BaseContract):
|
|
610
407
|
# mypy types
|
web3/datastructures.py
CHANGED
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
Tuple,
|
|
18
18
|
TypeVar,
|
|
19
19
|
Union,
|
|
20
|
+
ValuesView,
|
|
20
21
|
cast,
|
|
21
22
|
)
|
|
22
23
|
|
|
@@ -337,3 +338,6 @@ class NamedElementOnion(Mapping[TKey, TValue]):
|
|
|
337
338
|
# This leads to typing issues, so it's better to use
|
|
338
339
|
# ``as_tuple_of_middleware()`` to achieve the same result.
|
|
339
340
|
return iter(self._reversed_middleware()) # type: ignore
|
|
341
|
+
|
|
342
|
+
def values(self) -> ValuesView[TValue]:
|
|
343
|
+
return ValuesView(self._queue)
|
web3/eth/async_eth.py
CHANGED
|
@@ -7,6 +7,7 @@ from typing import (
|
|
|
7
7
|
Dict,
|
|
8
8
|
List,
|
|
9
9
|
Optional,
|
|
10
|
+
Sequence,
|
|
10
11
|
Tuple,
|
|
11
12
|
Type,
|
|
12
13
|
Union,
|
|
@@ -89,6 +90,8 @@ from web3.types import (
|
|
|
89
90
|
LogsSubscriptionArg,
|
|
90
91
|
Nonce,
|
|
91
92
|
SignedTx,
|
|
93
|
+
SimulateV1Payload,
|
|
94
|
+
SimulateV1Result,
|
|
92
95
|
StateOverride,
|
|
93
96
|
SubscriptionType,
|
|
94
97
|
SyncStatus,
|
|
@@ -288,6 +291,22 @@ class AsyncEth(BaseEth):
|
|
|
288
291
|
|
|
289
292
|
raise TooManyRequests("Too many CCIP read redirects")
|
|
290
293
|
|
|
294
|
+
# eth_simulateV1
|
|
295
|
+
|
|
296
|
+
_simulateV1: Method[
|
|
297
|
+
Callable[
|
|
298
|
+
[SimulateV1Payload, BlockIdentifier],
|
|
299
|
+
Awaitable[Sequence[SimulateV1Result]],
|
|
300
|
+
]
|
|
301
|
+
] = Method(RPC.eth_simulateV1)
|
|
302
|
+
|
|
303
|
+
async def simulate_v1(
|
|
304
|
+
self,
|
|
305
|
+
payload: SimulateV1Payload,
|
|
306
|
+
block_identifier: BlockIdentifier,
|
|
307
|
+
) -> Sequence[SimulateV1Result]:
|
|
308
|
+
return await self._simulateV1(payload, block_identifier)
|
|
309
|
+
|
|
291
310
|
# eth_createAccessList
|
|
292
311
|
|
|
293
312
|
_create_access_list: Method[
|
web3/eth/eth.py
CHANGED
|
@@ -85,6 +85,8 @@ from web3.types import (
|
|
|
85
85
|
MerkleProof,
|
|
86
86
|
Nonce,
|
|
87
87
|
SignedTx,
|
|
88
|
+
SimulateV1Payload,
|
|
89
|
+
SimulateV1Result,
|
|
88
90
|
StateOverride,
|
|
89
91
|
SyncStatus,
|
|
90
92
|
TxData,
|
|
@@ -270,6 +272,19 @@ class Eth(BaseEth):
|
|
|
270
272
|
|
|
271
273
|
raise TooManyRequests("Too many CCIP read redirects")
|
|
272
274
|
|
|
275
|
+
# eth_simulateV1
|
|
276
|
+
|
|
277
|
+
_simulateV1: Method[
|
|
278
|
+
Callable[[SimulateV1Payload, BlockIdentifier], Sequence[SimulateV1Result]]
|
|
279
|
+
] = Method(RPC.eth_simulateV1)
|
|
280
|
+
|
|
281
|
+
def simulate_v1(
|
|
282
|
+
self,
|
|
283
|
+
payload: SimulateV1Payload,
|
|
284
|
+
block_identifier: BlockIdentifier,
|
|
285
|
+
) -> Sequence[SimulateV1Result]:
|
|
286
|
+
return self._simulateV1(payload, block_identifier)
|
|
287
|
+
|
|
273
288
|
# eth_createAccessList
|
|
274
289
|
|
|
275
290
|
_create_access_list: Method[
|
|
@@ -158,7 +158,7 @@ def _compute_gas_price(
|
|
|
158
158
|
|
|
159
159
|
:param probabilities: An iterable of `Probability` named-tuples
|
|
160
160
|
sorted in reverse order.
|
|
161
|
-
:param desired_probability:
|
|
161
|
+
:param desired_probability: A floating point representation of the desired
|
|
162
162
|
probability. (e.g. ``85% -> 0.85``)
|
|
163
163
|
"""
|
|
164
164
|
first = probabilities[0]
|
web3/manager.py
CHANGED
|
@@ -31,17 +31,19 @@ from web3._utils.caching import (
|
|
|
31
31
|
from web3._utils.compat import (
|
|
32
32
|
Self,
|
|
33
33
|
)
|
|
34
|
+
from web3._utils.formatters import (
|
|
35
|
+
apply_null_result_formatters,
|
|
36
|
+
)
|
|
37
|
+
from web3._utils.validation import (
|
|
38
|
+
raise_error_for_batch_response,
|
|
39
|
+
validate_rpc_response_and_raise_if_error,
|
|
40
|
+
)
|
|
34
41
|
from web3.datastructures import (
|
|
35
42
|
NamedElementOnion,
|
|
36
43
|
)
|
|
37
44
|
from web3.exceptions import (
|
|
38
|
-
BadResponseFormat,
|
|
39
|
-
MethodUnavailable,
|
|
40
45
|
ProviderConnectionError,
|
|
41
|
-
RequestTimedOut,
|
|
42
46
|
TaskNotRunning,
|
|
43
|
-
TransactionNotFound,
|
|
44
|
-
Web3RPCError,
|
|
45
47
|
Web3TypeError,
|
|
46
48
|
)
|
|
47
49
|
from web3.method import (
|
|
@@ -94,176 +96,6 @@ if TYPE_CHECKING:
|
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
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
|
-
}
|
|
104
|
-
METHOD_NOT_FOUND = -32601
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _raise_bad_response_format(response: RPCResponse, error: str = "") -> None:
|
|
108
|
-
message = "The response was in an unexpected format and unable to be parsed."
|
|
109
|
-
raw_response = f"The raw response is: {response}"
|
|
110
|
-
|
|
111
|
-
if error is not None and error != "":
|
|
112
|
-
error = error[:-1] if error.endswith(".") else error
|
|
113
|
-
message = f"{message} {error}. {raw_response}"
|
|
114
|
-
else:
|
|
115
|
-
message = f"{message} {raw_response}"
|
|
116
|
-
|
|
117
|
-
raise BadResponseFormat(message)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def apply_error_formatters(
|
|
121
|
-
error_formatters: Callable[..., Any],
|
|
122
|
-
response: RPCResponse,
|
|
123
|
-
) -> RPCResponse:
|
|
124
|
-
if error_formatters:
|
|
125
|
-
formatted_resp = pipe(response, error_formatters)
|
|
126
|
-
return formatted_resp
|
|
127
|
-
else:
|
|
128
|
-
return response
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def apply_null_result_formatters(
|
|
132
|
-
null_result_formatters: Callable[..., Any],
|
|
133
|
-
response: RPCResponse,
|
|
134
|
-
params: Optional[Any] = None,
|
|
135
|
-
) -> RPCResponse:
|
|
136
|
-
if null_result_formatters:
|
|
137
|
-
formatted_resp = pipe(params, null_result_formatters)
|
|
138
|
-
return formatted_resp
|
|
139
|
-
else:
|
|
140
|
-
return response
|
|
141
|
-
|
|
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
99
|
|
|
268
100
|
|
|
269
101
|
class RequestManager:
|
|
@@ -363,7 +195,7 @@ class RequestManager:
|
|
|
363
195
|
and response["params"].get("result") is not None
|
|
364
196
|
)
|
|
365
197
|
|
|
366
|
-
|
|
198
|
+
validate_rpc_response_and_raise_if_error(
|
|
367
199
|
response,
|
|
368
200
|
error_formatters,
|
|
369
201
|
is_subscription_response=is_subscription_response,
|
|
@@ -422,6 +254,8 @@ class RequestManager:
|
|
|
422
254
|
"""
|
|
423
255
|
Context manager for making batch requests
|
|
424
256
|
"""
|
|
257
|
+
if isinstance(self.provider, AutoProvider):
|
|
258
|
+
self.provider = self.provider._get_active_provider(use_cache=True)
|
|
425
259
|
if not isinstance(self.provider, (AsyncJSONBaseProvider, JSONBaseProvider)):
|
|
426
260
|
raise Web3TypeError("Batch requests are not supported by this provider.")
|
|
427
261
|
return RequestBatcher(self.w3)
|
|
@@ -436,17 +270,23 @@ class RequestManager:
|
|
|
436
270
|
request_func = provider.batch_request_func(
|
|
437
271
|
cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
438
272
|
)
|
|
439
|
-
|
|
273
|
+
response = request_func(
|
|
440
274
|
[
|
|
441
275
|
(method, params)
|
|
442
276
|
for (method, params), _response_formatters in requests_info
|
|
443
277
|
]
|
|
444
278
|
)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
279
|
+
|
|
280
|
+
if isinstance(response, list):
|
|
281
|
+
# expected format
|
|
282
|
+
formatted_responses = [
|
|
283
|
+
self._format_batched_response(info, cast(RPCResponse, resp))
|
|
284
|
+
for info, resp in zip(requests_info, response)
|
|
285
|
+
]
|
|
286
|
+
return list(formatted_responses)
|
|
287
|
+
else:
|
|
288
|
+
# expect a single response with an error
|
|
289
|
+
raise_error_for_batch_response(response, self.logger)
|
|
450
290
|
|
|
451
291
|
async def _async_make_batch_request(
|
|
452
292
|
self,
|
|
@@ -465,25 +305,31 @@ class RequestManager:
|
|
|
465
305
|
# since we add items to the batch without awaiting, we unpack the coroutines
|
|
466
306
|
# and await them all here
|
|
467
307
|
unpacked_requests_info = await asyncio.gather(*requests_info)
|
|
468
|
-
|
|
308
|
+
response = await request_func(
|
|
469
309
|
[
|
|
470
310
|
(method, params)
|
|
471
311
|
for (method, params), _response_formatters in unpacked_requests_info
|
|
472
312
|
]
|
|
473
313
|
)
|
|
474
314
|
|
|
475
|
-
if isinstance(
|
|
476
|
-
#
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
for
|
|
315
|
+
if isinstance(response, list):
|
|
316
|
+
# expected format
|
|
317
|
+
response = cast(List[RPCResponse], response)
|
|
318
|
+
if isinstance(self.provider, PersistentConnectionProvider):
|
|
319
|
+
# call _process_response for each response in the batch
|
|
320
|
+
return [
|
|
321
|
+
cast(RPCResponse, await self._process_response(resp))
|
|
322
|
+
for resp in response
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
formatted_responses = [
|
|
326
|
+
self._format_batched_response(info, resp)
|
|
327
|
+
for info, resp in zip(unpacked_requests_info, response)
|
|
480
328
|
]
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
]
|
|
486
|
-
return list(formatted_responses)
|
|
329
|
+
return list(formatted_responses)
|
|
330
|
+
else:
|
|
331
|
+
# expect a single response with an error
|
|
332
|
+
raise_error_for_batch_response(response, self.logger)
|
|
487
333
|
|
|
488
334
|
def _format_batched_response(
|
|
489
335
|
self,
|
|
@@ -491,6 +337,13 @@ class RequestManager:
|
|
|
491
337
|
response: RPCResponse,
|
|
492
338
|
) -> RPCResponse:
|
|
493
339
|
result_formatters, error_formatters, null_result_formatters = requests_info[1]
|
|
340
|
+
validate_rpc_response_and_raise_if_error(
|
|
341
|
+
response,
|
|
342
|
+
error_formatters,
|
|
343
|
+
is_subscription_response=False,
|
|
344
|
+
logger=self.logger,
|
|
345
|
+
params=requests_info[0][1],
|
|
346
|
+
)
|
|
494
347
|
return apply_result_formatters(
|
|
495
348
|
result_formatters,
|
|
496
349
|
self.formatted_response(
|