web3 7.0.0b4__py3-none-any.whl → 7.0.0b6__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.
- web3/_utils/batching.py +217 -0
- web3/_utils/caching.py +26 -2
- web3/_utils/compat/__init__.py +1 -0
- web3/_utils/contracts.py +5 -5
- web3/_utils/events.py +20 -20
- web3/_utils/filters.py +6 -6
- web3/_utils/method_formatters.py +0 -23
- web3/_utils/module_testing/__init__.py +0 -3
- web3/_utils/module_testing/eth_module.py +442 -373
- web3/_utils/module_testing/module_testing_utils.py +13 -0
- web3/_utils/module_testing/web3_module.py +438 -17
- web3/_utils/rpc_abi.py +0 -18
- web3/contract/async_contract.py +11 -11
- web3/contract/base_contract.py +19 -18
- web3/contract/contract.py +13 -13
- web3/contract/utils.py +112 -4
- web3/eth/async_eth.py +10 -8
- web3/eth/eth.py +7 -6
- web3/exceptions.py +75 -21
- web3/gas_strategies/time_based.py +2 -2
- web3/geth.py +0 -188
- web3/main.py +21 -13
- web3/manager.py +237 -74
- web3/method.py +29 -9
- web3/middleware/base.py +43 -0
- web3/middleware/filter.py +18 -6
- web3/middleware/signing.py +2 -2
- web3/module.py +47 -7
- web3/providers/async_base.py +55 -23
- web3/providers/base.py +59 -26
- web3/providers/eth_tester/defaults.py +0 -48
- web3/providers/eth_tester/main.py +36 -11
- web3/providers/eth_tester/middleware.py +3 -8
- web3/providers/ipc.py +23 -8
- web3/providers/legacy_websocket.py +26 -1
- web3/providers/persistent/async_ipc.py +60 -76
- web3/providers/persistent/persistent.py +134 -10
- web3/providers/persistent/request_processor.py +98 -14
- web3/providers/persistent/websocket.py +43 -66
- web3/providers/rpc/async_rpc.py +20 -2
- web3/providers/rpc/rpc.py +22 -2
- web3/providers/rpc/utils.py +1 -10
- web3/tools/benchmark/node.py +2 -8
- web3/types.py +8 -2
- {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/LICENSE +1 -1
- {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/METADATA +32 -21
- {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/RECORD +49 -49
- web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
- {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/WHEEL +0 -0
- {web3-7.0.0b4.dist-info → web3-7.0.0b6.dist-info}/top_level.txt +0 -0
web3/manager.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
3
|
from typing import (
|
|
3
4
|
TYPE_CHECKING,
|
|
4
5
|
Any,
|
|
5
6
|
AsyncGenerator,
|
|
6
7
|
Callable,
|
|
8
|
+
Coroutine,
|
|
7
9
|
List,
|
|
8
10
|
Optional,
|
|
9
11
|
Sequence,
|
|
@@ -22,6 +24,9 @@ from websockets.exceptions import (
|
|
|
22
24
|
ConnectionClosedOK,
|
|
23
25
|
)
|
|
24
26
|
|
|
27
|
+
from web3._utils.batching import (
|
|
28
|
+
RequestBatcher,
|
|
29
|
+
)
|
|
25
30
|
from web3._utils.caching import (
|
|
26
31
|
generate_cache_key,
|
|
27
32
|
)
|
|
@@ -35,8 +40,12 @@ from web3.exceptions import (
|
|
|
35
40
|
BadResponseFormat,
|
|
36
41
|
MethodUnavailable,
|
|
37
42
|
ProviderConnectionError,
|
|
43
|
+
TaskNotRunning,
|
|
44
|
+
Web3RPCError,
|
|
38
45
|
Web3TypeError,
|
|
39
|
-
|
|
46
|
+
)
|
|
47
|
+
from web3.method import (
|
|
48
|
+
Method,
|
|
40
49
|
)
|
|
41
50
|
from web3.middleware import (
|
|
42
51
|
AttributeDictMiddleware,
|
|
@@ -54,8 +63,12 @@ from web3.module import (
|
|
|
54
63
|
)
|
|
55
64
|
from web3.providers import (
|
|
56
65
|
AutoProvider,
|
|
66
|
+
JSONBaseProvider,
|
|
57
67
|
PersistentConnectionProvider,
|
|
58
68
|
)
|
|
69
|
+
from web3.providers.async_base import (
|
|
70
|
+
AsyncJSONBaseProvider,
|
|
71
|
+
)
|
|
59
72
|
from web3.types import (
|
|
60
73
|
RPCEndpoint,
|
|
61
74
|
RPCResponse,
|
|
@@ -87,6 +100,7 @@ def _raise_bad_response_format(response: RPCResponse, error: str = "") -> None:
|
|
|
87
100
|
raw_response = f"The raw response is: {response}"
|
|
88
101
|
|
|
89
102
|
if error is not None and error != "":
|
|
103
|
+
error = error[:-1] if error.endswith(".") else error
|
|
90
104
|
message = f"{message} {error}. {raw_response}"
|
|
91
105
|
else:
|
|
92
106
|
message = f"{message} {raw_response}"
|
|
@@ -117,6 +131,111 @@ def apply_null_result_formatters(
|
|
|
117
131
|
return response
|
|
118
132
|
|
|
119
133
|
|
|
134
|
+
def _validate_subscription_fields(response: RPCResponse) -> None:
|
|
135
|
+
params = response["params"]
|
|
136
|
+
subscription = params["subscription"]
|
|
137
|
+
if not isinstance(subscription, str) and not len(subscription) == 34:
|
|
138
|
+
_raise_bad_response_format(
|
|
139
|
+
response, "eth_subscription 'params' must include a 'subscription' field."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _validate_response(
|
|
144
|
+
response: RPCResponse,
|
|
145
|
+
error_formatters: Optional[Callable[..., Any]],
|
|
146
|
+
is_subscription_response: bool = False,
|
|
147
|
+
logger: Optional[logging.Logger] = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
if "jsonrpc" not in response or response["jsonrpc"] != "2.0":
|
|
150
|
+
_raise_bad_response_format(
|
|
151
|
+
response, 'The "jsonrpc" field must be present with a value of "2.0".'
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
response_id = response.get("id")
|
|
155
|
+
if "id" in response:
|
|
156
|
+
int_error_msg = (
|
|
157
|
+
'"id" must be an integer or a string representation of an integer.'
|
|
158
|
+
)
|
|
159
|
+
if response_id is None and "error" in response:
|
|
160
|
+
# errors can sometimes have null `id`, according to the JSON-RPC spec
|
|
161
|
+
pass
|
|
162
|
+
elif not isinstance(response_id, (str, int)):
|
|
163
|
+
_raise_bad_response_format(response, int_error_msg)
|
|
164
|
+
elif isinstance(response_id, str):
|
|
165
|
+
try:
|
|
166
|
+
int(response_id)
|
|
167
|
+
except ValueError:
|
|
168
|
+
_raise_bad_response_format(response, int_error_msg)
|
|
169
|
+
elif is_subscription_response:
|
|
170
|
+
# if `id` is not present, this must be a subscription response
|
|
171
|
+
_validate_subscription_fields(response)
|
|
172
|
+
else:
|
|
173
|
+
_raise_bad_response_format(
|
|
174
|
+
response,
|
|
175
|
+
'Response must include an "id" field or be formatted as an '
|
|
176
|
+
"`eth_subscription` response.",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if all(key in response for key in {"error", "result"}):
|
|
180
|
+
_raise_bad_response_format(
|
|
181
|
+
response, 'Response cannot include both "error" and "result".'
|
|
182
|
+
)
|
|
183
|
+
elif (
|
|
184
|
+
not any(key in response for key in {"error", "result"})
|
|
185
|
+
and not is_subscription_response
|
|
186
|
+
):
|
|
187
|
+
_raise_bad_response_format(
|
|
188
|
+
response, 'Response must include either "error" or "result".'
|
|
189
|
+
)
|
|
190
|
+
elif "error" in response:
|
|
191
|
+
error = response["error"]
|
|
192
|
+
|
|
193
|
+
# raise the error when the value is a string
|
|
194
|
+
if error is None or not isinstance(error, dict):
|
|
195
|
+
_raise_bad_response_format(
|
|
196
|
+
response,
|
|
197
|
+
'response["error"] must be a valid object as defined by the '
|
|
198
|
+
"JSON-RPC 2.0 specification.",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# errors must include an integer code
|
|
202
|
+
code = error.get("code")
|
|
203
|
+
if not isinstance(code, int):
|
|
204
|
+
_raise_bad_response_format(
|
|
205
|
+
response, 'error["code"] is required and must be an integer value.'
|
|
206
|
+
)
|
|
207
|
+
elif code == METHOD_NOT_FOUND:
|
|
208
|
+
exception = MethodUnavailable(
|
|
209
|
+
repr(error),
|
|
210
|
+
rpc_response=response,
|
|
211
|
+
user_message=(
|
|
212
|
+
"This method is not available. Check your node provider or your "
|
|
213
|
+
"client's API docs to see what methods are supported and / or "
|
|
214
|
+
"currently enabled."
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
logger.error(exception.user_message)
|
|
218
|
+
logger.debug(f"RPC error response: {response}")
|
|
219
|
+
raise exception
|
|
220
|
+
|
|
221
|
+
# errors must include a message
|
|
222
|
+
error_message = error.get("message")
|
|
223
|
+
if not isinstance(error_message, str):
|
|
224
|
+
_raise_bad_response_format(
|
|
225
|
+
response, 'error["message"] is required and must be a string value.'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
apply_error_formatters(error_formatters, response)
|
|
229
|
+
|
|
230
|
+
web3_rpc_error = Web3RPCError(repr(error), rpc_response=response)
|
|
231
|
+
logger.error(web3_rpc_error.user_message)
|
|
232
|
+
logger.debug(f"RPC error response: {response}")
|
|
233
|
+
raise web3_rpc_error
|
|
234
|
+
|
|
235
|
+
elif "result" not in response and not is_subscription_response:
|
|
236
|
+
_raise_bad_response_format(response)
|
|
237
|
+
|
|
238
|
+
|
|
120
239
|
class RequestManager:
|
|
121
240
|
logger = logging.getLogger("web3.manager.RequestManager")
|
|
122
241
|
|
|
@@ -203,90 +322,42 @@ class RequestManager:
|
|
|
203
322
|
#
|
|
204
323
|
# See also: https://www.jsonrpc.org/specification
|
|
205
324
|
#
|
|
206
|
-
@staticmethod
|
|
207
325
|
def formatted_response(
|
|
326
|
+
self,
|
|
208
327
|
response: RPCResponse,
|
|
209
328
|
params: Any,
|
|
210
329
|
error_formatters: Optional[Callable[..., Any]] = None,
|
|
211
330
|
null_result_formatters: Optional[Callable[..., Any]] = None,
|
|
212
331
|
) -> Any:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
# id is not enforced (as per the spec) but if present, it must be a
|
|
220
|
-
# string or integer
|
|
221
|
-
# TODO: v7 - enforce id per the spec
|
|
222
|
-
if "id" in response:
|
|
223
|
-
response_id = response["id"]
|
|
224
|
-
# id is always None for errors
|
|
225
|
-
if response_id is None and "error" not in response:
|
|
226
|
-
_raise_bad_response_format(
|
|
227
|
-
response, '"id" must be None when an error is present'
|
|
228
|
-
)
|
|
229
|
-
elif not isinstance(response_id, (str, int, type(None))):
|
|
230
|
-
_raise_bad_response_format(response, '"id" must be a string or integer')
|
|
231
|
-
|
|
232
|
-
# Response may not include both "error" and "result"
|
|
233
|
-
if "error" in response and "result" in response:
|
|
234
|
-
_raise_bad_response_format(
|
|
235
|
-
response, 'Response cannot include both "error" and "result"'
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
# Format and validate errors
|
|
239
|
-
elif "error" in response:
|
|
240
|
-
error = response.get("error")
|
|
241
|
-
# Raise the error when the value is a string
|
|
242
|
-
if error is None or isinstance(error, str):
|
|
243
|
-
raise Web3ValueError(error)
|
|
244
|
-
|
|
245
|
-
# Errors must include an integer code
|
|
246
|
-
code = error.get("code")
|
|
247
|
-
if not isinstance(code, int):
|
|
248
|
-
_raise_bad_response_format(response, "error['code'] must be an integer")
|
|
249
|
-
elif code == METHOD_NOT_FOUND:
|
|
250
|
-
raise MethodUnavailable(
|
|
251
|
-
error,
|
|
252
|
-
user_message="Check your node provider's API docs to see what "
|
|
253
|
-
"methods are supported",
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
# Errors must include a message
|
|
257
|
-
if not isinstance(error.get("message"), str):
|
|
258
|
-
_raise_bad_response_format(
|
|
259
|
-
response, "error['message'] must be a string"
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
apply_error_formatters(error_formatters, response)
|
|
332
|
+
is_subscription_response = (
|
|
333
|
+
response.get("method") == "eth_subscription"
|
|
334
|
+
and response.get("params") is not None
|
|
335
|
+
and response["params"].get("subscription") is not None
|
|
336
|
+
and response["params"].get("result") is not None
|
|
337
|
+
)
|
|
263
338
|
|
|
264
|
-
|
|
339
|
+
_validate_response(
|
|
340
|
+
response,
|
|
341
|
+
error_formatters,
|
|
342
|
+
is_subscription_response=is_subscription_response,
|
|
343
|
+
logger=self.logger,
|
|
344
|
+
)
|
|
265
345
|
|
|
266
|
-
#
|
|
267
|
-
|
|
346
|
+
# format results
|
|
347
|
+
if "result" in response:
|
|
268
348
|
# Null values for result should apply null_result_formatters
|
|
269
349
|
# Skip when result not present in the response (fallback to False)
|
|
270
350
|
if response.get("result", False) in NULL_RESPONSES:
|
|
271
351
|
apply_null_result_formatters(null_result_formatters, response, params)
|
|
272
352
|
return response.get("result")
|
|
273
353
|
|
|
274
|
-
#
|
|
275
|
-
elif
|
|
276
|
-
response.get("method") == "eth_subscription"
|
|
277
|
-
and response.get("params") is not None
|
|
278
|
-
and response["params"].get("subscription") is not None
|
|
279
|
-
and response["params"].get("result") is not None
|
|
280
|
-
):
|
|
354
|
+
# response from eth_subscription includes response["params"]["result"]
|
|
355
|
+
elif is_subscription_response:
|
|
281
356
|
return {
|
|
282
357
|
"subscription": response["params"]["subscription"],
|
|
283
358
|
"result": response["params"]["result"],
|
|
284
359
|
}
|
|
285
360
|
|
|
286
|
-
# Any other response type raises BadResponseFormat
|
|
287
|
-
else:
|
|
288
|
-
_raise_bad_response_format(response)
|
|
289
|
-
|
|
290
361
|
def request_blocking(
|
|
291
362
|
self,
|
|
292
363
|
method: Union[RPCEndpoint, Callable[..., RPCEndpoint]],
|
|
@@ -317,6 +388,88 @@ class RequestManager:
|
|
|
317
388
|
response, params, error_formatters, null_result_formatters
|
|
318
389
|
)
|
|
319
390
|
|
|
391
|
+
# -- batch requests management -- #
|
|
392
|
+
|
|
393
|
+
def _batch_requests(self) -> RequestBatcher[Method[Callable[..., Any]]]:
|
|
394
|
+
"""
|
|
395
|
+
Context manager for making batch requests
|
|
396
|
+
"""
|
|
397
|
+
if not isinstance(self.provider, (AsyncJSONBaseProvider, JSONBaseProvider)):
|
|
398
|
+
raise Web3TypeError("Batch requests are not supported by this provider.")
|
|
399
|
+
return RequestBatcher(self.w3)
|
|
400
|
+
|
|
401
|
+
def _make_batch_request(
|
|
402
|
+
self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
|
|
403
|
+
) -> List[RPCResponse]:
|
|
404
|
+
"""
|
|
405
|
+
Make a batch request using the provider
|
|
406
|
+
"""
|
|
407
|
+
provider = cast(JSONBaseProvider, self.provider)
|
|
408
|
+
request_func = provider.batch_request_func(
|
|
409
|
+
cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
410
|
+
)
|
|
411
|
+
responses = request_func(
|
|
412
|
+
[
|
|
413
|
+
(method, params)
|
|
414
|
+
for (method, params), _response_formatters in requests_info
|
|
415
|
+
]
|
|
416
|
+
)
|
|
417
|
+
formatted_responses = [
|
|
418
|
+
self._format_batched_response(info, resp)
|
|
419
|
+
for info, resp in zip(requests_info, responses)
|
|
420
|
+
]
|
|
421
|
+
return list(formatted_responses)
|
|
422
|
+
|
|
423
|
+
async def _async_make_batch_request(
|
|
424
|
+
self,
|
|
425
|
+
requests_info: List[
|
|
426
|
+
Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Sequence[Any]]]
|
|
427
|
+
],
|
|
428
|
+
) -> List[RPCResponse]:
|
|
429
|
+
"""
|
|
430
|
+
Make an asynchronous batch request using the provider
|
|
431
|
+
"""
|
|
432
|
+
provider = cast(AsyncJSONBaseProvider, self.provider)
|
|
433
|
+
request_func = await provider.batch_request_func(
|
|
434
|
+
cast("AsyncWeb3", self.w3),
|
|
435
|
+
cast("MiddlewareOnion", self.middleware_onion),
|
|
436
|
+
)
|
|
437
|
+
# since we add items to the batch without awaiting, we unpack the coroutines
|
|
438
|
+
# and await them all here
|
|
439
|
+
unpacked_requests_info = await asyncio.gather(*requests_info)
|
|
440
|
+
responses = await request_func(
|
|
441
|
+
[
|
|
442
|
+
(method, params)
|
|
443
|
+
for (method, params), _response_formatters in unpacked_requests_info
|
|
444
|
+
]
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if isinstance(self.provider, PersistentConnectionProvider):
|
|
448
|
+
# call _process_response for each response in the batch
|
|
449
|
+
return [await self._process_response(resp) for resp in responses]
|
|
450
|
+
|
|
451
|
+
formatted_responses = [
|
|
452
|
+
self._format_batched_response(info, resp)
|
|
453
|
+
for info, resp in zip(unpacked_requests_info, responses)
|
|
454
|
+
]
|
|
455
|
+
return list(formatted_responses)
|
|
456
|
+
|
|
457
|
+
def _format_batched_response(
|
|
458
|
+
self,
|
|
459
|
+
requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
|
|
460
|
+
response: RPCResponse,
|
|
461
|
+
) -> RPCResponse:
|
|
462
|
+
result_formatters, error_formatters, null_result_formatters = requests_info[1]
|
|
463
|
+
return apply_result_formatters(
|
|
464
|
+
result_formatters,
|
|
465
|
+
self.formatted_response(
|
|
466
|
+
response,
|
|
467
|
+
requests_info[0][1],
|
|
468
|
+
error_formatters,
|
|
469
|
+
null_result_formatters,
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
|
|
320
473
|
# -- persistent connection -- #
|
|
321
474
|
|
|
322
475
|
async def send(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
@@ -350,14 +503,24 @@ class RequestManager:
|
|
|
350
503
|
)
|
|
351
504
|
|
|
352
505
|
while True:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
506
|
+
try:
|
|
507
|
+
response = await self._request_processor.pop_raw_response(
|
|
508
|
+
subscription=True
|
|
509
|
+
)
|
|
510
|
+
if (
|
|
511
|
+
response is not None
|
|
512
|
+
and response.get("params", {}).get("subscription")
|
|
513
|
+
in self._request_processor.active_subscriptions
|
|
514
|
+
):
|
|
515
|
+
# if response is an active subscription response, process it
|
|
516
|
+
yield await self._process_response(response)
|
|
517
|
+
except TaskNotRunning:
|
|
518
|
+
self._provider._handle_listener_task_exceptions()
|
|
519
|
+
self.logger.error(
|
|
520
|
+
"Message listener background task has stopped unexpectedly. "
|
|
521
|
+
"Stopping message stream."
|
|
522
|
+
)
|
|
523
|
+
raise StopAsyncIteration
|
|
361
524
|
|
|
362
525
|
async def _process_response(self, response: RPCResponse) -> RPCResponse:
|
|
363
526
|
provider = cast(PersistentConnectionProvider, self._provider)
|
web3/method.py
CHANGED
|
@@ -10,7 +10,6 @@ from typing import (
|
|
|
10
10
|
Sequence,
|
|
11
11
|
Tuple,
|
|
12
12
|
Type,
|
|
13
|
-
TypeVar,
|
|
14
13
|
Union,
|
|
15
14
|
)
|
|
16
15
|
import warnings
|
|
@@ -22,6 +21,9 @@ from eth_utils.toolz import (
|
|
|
22
21
|
pipe,
|
|
23
22
|
)
|
|
24
23
|
|
|
24
|
+
from web3._utils.batching import (
|
|
25
|
+
RPC_METHODS_UNSUPPORTED_DURING_BATCH,
|
|
26
|
+
)
|
|
25
27
|
from web3._utils.method_formatters import (
|
|
26
28
|
get_error_formatters,
|
|
27
29
|
get_null_result_formatters,
|
|
@@ -32,17 +34,22 @@ from web3._utils.rpc_abi import (
|
|
|
32
34
|
RPC,
|
|
33
35
|
)
|
|
34
36
|
from web3.exceptions import (
|
|
37
|
+
MethodNotSupported,
|
|
35
38
|
Web3TypeError,
|
|
36
39
|
Web3ValidationError,
|
|
37
40
|
Web3ValueError,
|
|
38
41
|
)
|
|
39
42
|
from web3.types import (
|
|
40
43
|
RPCEndpoint,
|
|
44
|
+
TFunc,
|
|
41
45
|
TReturn,
|
|
42
46
|
)
|
|
43
47
|
|
|
44
48
|
if TYPE_CHECKING:
|
|
45
|
-
from web3 import
|
|
49
|
+
from web3 import ( # noqa: F401
|
|
50
|
+
PersistentConnectionProvider,
|
|
51
|
+
Web3,
|
|
52
|
+
)
|
|
46
53
|
from web3.module import Module # noqa: F401
|
|
47
54
|
|
|
48
55
|
|
|
@@ -84,9 +91,6 @@ def default_root_munger(_module: "Module", *args: Any) -> List[Any]:
|
|
|
84
91
|
return [*args]
|
|
85
92
|
|
|
86
93
|
|
|
87
|
-
TFunc = TypeVar("TFunc", bound=Callable[..., Any])
|
|
88
|
-
|
|
89
|
-
|
|
90
94
|
class Method(Generic[TFunc]):
|
|
91
95
|
"""
|
|
92
96
|
Method object for web3 module methods
|
|
@@ -149,15 +153,31 @@ class Method(Generic[TFunc]):
|
|
|
149
153
|
self.is_property = is_property
|
|
150
154
|
|
|
151
155
|
def __get__(
|
|
152
|
-
self,
|
|
156
|
+
self,
|
|
157
|
+
module: Optional["Module"] = None,
|
|
158
|
+
_type: Optional[Type["Module"]] = None,
|
|
153
159
|
) -> TFunc:
|
|
154
|
-
|
|
160
|
+
self._module = module
|
|
161
|
+
if module is None:
|
|
155
162
|
raise Web3TypeError(
|
|
156
163
|
"Direct calls to methods are not supported. "
|
|
157
|
-
"Methods must be called from
|
|
164
|
+
"Methods must be called from a module instance, "
|
|
158
165
|
"usually attached to a web3 instance."
|
|
159
166
|
)
|
|
160
|
-
|
|
167
|
+
|
|
168
|
+
provider = module.w3.provider
|
|
169
|
+
if hasattr(provider, "_is_batching") and provider._is_batching:
|
|
170
|
+
if self.json_rpc_method in RPC_METHODS_UNSUPPORTED_DURING_BATCH:
|
|
171
|
+
raise MethodNotSupported(
|
|
172
|
+
f"Method `{self.json_rpc_method}` is not supported within a batch "
|
|
173
|
+
"request."
|
|
174
|
+
)
|
|
175
|
+
return module.retrieve_request_information(self)
|
|
176
|
+
else:
|
|
177
|
+
return module.retrieve_caller_fn(self)
|
|
178
|
+
|
|
179
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
180
|
+
return self.__get__(self._module)(*args, **kwargs)
|
|
161
181
|
|
|
162
182
|
@property
|
|
163
183
|
def method_selector_fn(
|
web3/middleware/base.py
CHANGED
|
@@ -4,6 +4,8 @@ from abc import (
|
|
|
4
4
|
from typing import (
|
|
5
5
|
TYPE_CHECKING,
|
|
6
6
|
Any,
|
|
7
|
+
List,
|
|
8
|
+
Tuple,
|
|
7
9
|
Type,
|
|
8
10
|
Union,
|
|
9
11
|
)
|
|
@@ -18,7 +20,9 @@ if TYPE_CHECKING:
|
|
|
18
20
|
Web3,
|
|
19
21
|
)
|
|
20
22
|
from web3.types import ( # noqa: F401
|
|
23
|
+
AsyncMakeBatchRequestFn,
|
|
21
24
|
AsyncMakeRequestFn,
|
|
25
|
+
MakeBatchRequestFn,
|
|
22
26
|
MakeRequestFn,
|
|
23
27
|
RPCEndpoint,
|
|
24
28
|
RPCResponse,
|
|
@@ -45,6 +49,25 @@ class Web3Middleware:
|
|
|
45
49
|
|
|
46
50
|
return middleware
|
|
47
51
|
|
|
52
|
+
def wrap_make_batch_request(
|
|
53
|
+
self, make_batch_request: "MakeBatchRequestFn"
|
|
54
|
+
) -> "MakeBatchRequestFn":
|
|
55
|
+
def middleware(
|
|
56
|
+
requests_info: List[Tuple["RPCEndpoint", Any]]
|
|
57
|
+
) -> List["RPCResponse"]:
|
|
58
|
+
req_processed = [
|
|
59
|
+
self.request_processor(method, params)
|
|
60
|
+
for (method, params) in requests_info
|
|
61
|
+
]
|
|
62
|
+
responses = make_batch_request(req_processed)
|
|
63
|
+
methods, _params = zip(*req_processed)
|
|
64
|
+
formatted_responses = [
|
|
65
|
+
self.response_processor(m, r) for m, r in zip(methods, responses)
|
|
66
|
+
]
|
|
67
|
+
return formatted_responses
|
|
68
|
+
|
|
69
|
+
return middleware
|
|
70
|
+
|
|
48
71
|
def request_processor(self, method: "RPCEndpoint", params: Any) -> Any:
|
|
49
72
|
return method, params
|
|
50
73
|
|
|
@@ -67,6 +90,26 @@ class Web3Middleware:
|
|
|
67
90
|
|
|
68
91
|
return middleware
|
|
69
92
|
|
|
93
|
+
async def async_wrap_make_batch_request(
|
|
94
|
+
self, make_batch_request: "AsyncMakeBatchRequestFn"
|
|
95
|
+
) -> "AsyncMakeBatchRequestFn":
|
|
96
|
+
async def middleware(
|
|
97
|
+
requests_info: List[Tuple["RPCEndpoint", Any]]
|
|
98
|
+
) -> List["RPCResponse"]:
|
|
99
|
+
req_processed = [
|
|
100
|
+
await self.async_request_processor(method, params)
|
|
101
|
+
for (method, params) in requests_info
|
|
102
|
+
]
|
|
103
|
+
responses = await make_batch_request(req_processed)
|
|
104
|
+
methods, _params = zip(*req_processed)
|
|
105
|
+
formatted_responses = [
|
|
106
|
+
await self.async_response_processor(m, r)
|
|
107
|
+
for m, r in zip(methods, responses)
|
|
108
|
+
]
|
|
109
|
+
return formatted_responses
|
|
110
|
+
|
|
111
|
+
return middleware
|
|
112
|
+
|
|
70
113
|
async def async_request_processor(
|
|
71
114
|
self,
|
|
72
115
|
method: "RPCEndpoint",
|
web3/middleware/filter.py
CHANGED
|
@@ -589,6 +589,10 @@ SyncFilter = Union[RequestLogs, RequestBlocks]
|
|
|
589
589
|
AsyncFilter = Union[AsyncRequestLogs, AsyncRequestBlocks]
|
|
590
590
|
|
|
591
591
|
|
|
592
|
+
def _simulate_rpc_response_with_result(filter_id: str) -> "RPCResponse":
|
|
593
|
+
return {"jsonrpc": "2.0", "id": -1, "result": filter_id}
|
|
594
|
+
|
|
595
|
+
|
|
592
596
|
class LocalFilterMiddleware(Web3Middleware):
|
|
593
597
|
def __init__(self, w3: Union["Web3", "AsyncWeb3"]):
|
|
594
598
|
self.filters: Dict[str, SyncFilter] = {}
|
|
@@ -615,7 +619,7 @@ class LocalFilterMiddleware(Web3Middleware):
|
|
|
615
619
|
raise NotImplementedError(method)
|
|
616
620
|
|
|
617
621
|
self.filters[filter_id] = _filter
|
|
618
|
-
return
|
|
622
|
+
return _simulate_rpc_response_with_result(filter_id)
|
|
619
623
|
|
|
620
624
|
elif method in FILTER_CHANGES_METHODS:
|
|
621
625
|
_filter_id = params[0]
|
|
@@ -626,12 +630,16 @@ class LocalFilterMiddleware(Web3Middleware):
|
|
|
626
630
|
|
|
627
631
|
_filter = self.filters[_filter_id]
|
|
628
632
|
if method == RPC.eth_getFilterChanges:
|
|
629
|
-
return
|
|
633
|
+
return _simulate_rpc_response_with_result(
|
|
634
|
+
next(_filter.filter_changes) # type: ignore
|
|
635
|
+
)
|
|
630
636
|
|
|
631
637
|
elif method == RPC.eth_getFilterLogs:
|
|
632
638
|
# type ignored b/c logic prevents RequestBlocks which
|
|
633
639
|
# doesn't implement get_logs
|
|
634
|
-
return
|
|
640
|
+
return _simulate_rpc_response_with_result(
|
|
641
|
+
_filter.get_logs() # type: ignore
|
|
642
|
+
)
|
|
635
643
|
else:
|
|
636
644
|
raise NotImplementedError(method)
|
|
637
645
|
else:
|
|
@@ -663,7 +671,7 @@ class LocalFilterMiddleware(Web3Middleware):
|
|
|
663
671
|
raise NotImplementedError(method)
|
|
664
672
|
|
|
665
673
|
self.async_filters[filter_id] = _filter
|
|
666
|
-
return
|
|
674
|
+
return _simulate_rpc_response_with_result(filter_id)
|
|
667
675
|
|
|
668
676
|
elif method in FILTER_CHANGES_METHODS:
|
|
669
677
|
_filter_id = params[0]
|
|
@@ -674,12 +682,16 @@ class LocalFilterMiddleware(Web3Middleware):
|
|
|
674
682
|
|
|
675
683
|
_filter = self.async_filters[_filter_id]
|
|
676
684
|
if method == RPC.eth_getFilterChanges:
|
|
677
|
-
return
|
|
685
|
+
return _simulate_rpc_response_with_result(
|
|
686
|
+
await _filter.filter_changes.__anext__() # type: ignore
|
|
687
|
+
)
|
|
678
688
|
|
|
679
689
|
elif method == RPC.eth_getFilterLogs:
|
|
680
690
|
# type ignored b/c logic prevents RequestBlocks which
|
|
681
691
|
# doesn't implement get_logs
|
|
682
|
-
return
|
|
692
|
+
return _simulate_rpc_response_with_result(
|
|
693
|
+
await _filter.get_logs() # type: ignore
|
|
694
|
+
)
|
|
683
695
|
else:
|
|
684
696
|
raise NotImplementedError(method)
|
|
685
697
|
else:
|
web3/middleware/signing.py
CHANGED
|
@@ -182,7 +182,7 @@ class SignAndSendRawMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
182
182
|
return method, params
|
|
183
183
|
else:
|
|
184
184
|
account = self._accounts[to_checksum_address(tx_from)]
|
|
185
|
-
raw_tx = account.sign_transaction(filled_transaction).
|
|
185
|
+
raw_tx = account.sign_transaction(filled_transaction).raw_transaction
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
188
|
RPCEndpoint("eth_sendRawTransaction"),
|
|
@@ -211,7 +211,7 @@ class SignAndSendRawMiddlewareBuilder(Web3MiddlewareBuilder):
|
|
|
211
211
|
return method, params
|
|
212
212
|
else:
|
|
213
213
|
account = self._accounts[to_checksum_address(tx_from)]
|
|
214
|
-
raw_tx = account.sign_transaction(filled_transaction).
|
|
214
|
+
raw_tx = account.sign_transaction(filled_transaction).raw_transaction
|
|
215
215
|
|
|
216
216
|
return (
|
|
217
217
|
RPCEndpoint("eth_sendRawTransaction"),
|