web3 7.11.1__py3-none-any.whl → 7.12.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 +2 -2
- ens/ens.py +2 -2
- ens/utils.py +14 -3
- web3/_utils/abi.py +24 -20
- web3/_utils/batching.py +22 -68
- web3/_utils/caching/request_caching_validation.py +8 -4
- web3/_utils/decorators.py +12 -9
- web3/_utils/http_session_manager.py +18 -15
- web3/_utils/method_formatters.py +17 -24
- web3/_utils/module_testing/eth_module.py +25 -14
- web3/_utils/module_testing/web3_module.py +78 -4
- web3/_utils/validation.py +1 -1
- web3/contract/utils.py +20 -35
- web3/manager.py +105 -23
- web3/method.py +7 -7
- web3/providers/async_base.py +15 -1
- web3/providers/base.py +18 -5
- web3/providers/ipc.py +2 -4
- web3/providers/legacy_websocket.py +4 -5
- web3/providers/persistent/persistent.py +110 -40
- web3/providers/persistent/request_processor.py +34 -51
- web3/providers/persistent/subscription_manager.py +12 -7
- web3/providers/rpc/async_rpc.py +7 -7
- web3/providers/rpc/rpc.py +6 -6
- web3/utils/subscriptions.py +7 -4
- {web3-7.11.1.dist-info → web3-7.12.0.dist-info}/METADATA +69 -56
- {web3-7.11.1.dist-info → web3-7.12.0.dist-info}/RECORD +30 -30
- {web3-7.11.1.dist-info → web3-7.12.0.dist-info}/WHEEL +1 -1
- {web3-7.11.1.dist-info → web3-7.12.0.dist-info/licenses}/LICENSE +0 -0
- {web3-7.11.1.dist-info → web3-7.12.0.dist-info}/top_level.txt +0 -0
|
@@ -37,6 +37,7 @@ from eth_utils import (
|
|
|
37
37
|
)
|
|
38
38
|
from eth_utils.toolz import (
|
|
39
39
|
assoc,
|
|
40
|
+
merge,
|
|
40
41
|
)
|
|
41
42
|
from hexbytes import (
|
|
42
43
|
HexBytes,
|
|
@@ -717,7 +718,7 @@ class AsyncEthModuleTest:
|
|
|
717
718
|
async_w3.middleware_onion.remove("signing")
|
|
718
719
|
|
|
719
720
|
@pytest.mark.asyncio
|
|
720
|
-
async def
|
|
721
|
+
async def test_async_sign_authorization_send_raw_and_send_set_code_transactions(
|
|
721
722
|
self,
|
|
722
723
|
async_w3: "AsyncWeb3",
|
|
723
724
|
keyfile_account_pkey: HexStr,
|
|
@@ -752,6 +753,7 @@ class AsyncEthModuleTest:
|
|
|
752
753
|
"authorizationList": [signed_auth],
|
|
753
754
|
}
|
|
754
755
|
|
|
756
|
+
# test eth_sendRawTransaction
|
|
755
757
|
signed = keyfile_account.sign_transaction(txn)
|
|
756
758
|
tx_hash = await async_w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
757
759
|
get_tx = await async_w3.eth.get_transaction(tx_hash)
|
|
@@ -790,14 +792,17 @@ class AsyncEthModuleTest:
|
|
|
790
792
|
"nonce": nonce + 3,
|
|
791
793
|
}
|
|
792
794
|
signed_reset_auth = keyfile_account.sign_authorization(reset_auth)
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
795
|
+
reset_code_txn = merge(
|
|
796
|
+
txn,
|
|
797
|
+
{
|
|
798
|
+
"from": keyfile_account.address,
|
|
799
|
+
"authorizationList": [signed_reset_auth],
|
|
800
|
+
"nonce": nonce + 2,
|
|
801
|
+
},
|
|
800
802
|
)
|
|
803
|
+
|
|
804
|
+
# test eth_sendTransaction
|
|
805
|
+
reset_tx_hash = await async_w3.eth.send_transaction(reset_code_txn)
|
|
801
806
|
reset_tx_receipt = await async_w3.eth.wait_for_transaction_receipt(
|
|
802
807
|
reset_tx_hash, timeout=10
|
|
803
808
|
)
|
|
@@ -3867,7 +3872,7 @@ class EthModuleTest:
|
|
|
3867
3872
|
# cleanup
|
|
3868
3873
|
w3.middleware_onion.remove("signing")
|
|
3869
3874
|
|
|
3870
|
-
def
|
|
3875
|
+
def test_sign_authorization_send_raw_and_send_set_code_transactions(
|
|
3871
3876
|
self, w3: "Web3", keyfile_account_pkey: HexStr, math_contract: "Contract"
|
|
3872
3877
|
) -> None:
|
|
3873
3878
|
# TODO: remove blockNumber block_id from eth_call and eth_getCode calls once
|
|
@@ -3899,6 +3904,7 @@ class EthModuleTest:
|
|
|
3899
3904
|
"authorizationList": [signed_auth],
|
|
3900
3905
|
}
|
|
3901
3906
|
|
|
3907
|
+
# test eth_sendRawTransaction
|
|
3902
3908
|
signed = keyfile_account.sign_transaction(txn)
|
|
3903
3909
|
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
3904
3910
|
get_tx = w3.eth.get_transaction(tx_hash)
|
|
@@ -3939,12 +3945,17 @@ class EthModuleTest:
|
|
|
3939
3945
|
"nonce": nonce + 3,
|
|
3940
3946
|
}
|
|
3941
3947
|
signed_reset_auth = keyfile_account.sign_authorization(reset_auth)
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3948
|
+
reset_code_txn = merge(
|
|
3949
|
+
txn,
|
|
3950
|
+
{
|
|
3951
|
+
"from": keyfile_account.address,
|
|
3952
|
+
"authorizationList": [signed_reset_auth],
|
|
3953
|
+
"nonce": nonce + 2,
|
|
3954
|
+
},
|
|
3955
|
+
)
|
|
3945
3956
|
|
|
3946
|
-
|
|
3947
|
-
reset_tx_hash = w3.eth.
|
|
3957
|
+
# test eth_sendTransaction
|
|
3958
|
+
reset_tx_hash = w3.eth.send_transaction(reset_code_txn)
|
|
3948
3959
|
reset_tx_receipt = w3.eth.wait_for_transaction_receipt(
|
|
3949
3960
|
reset_tx_hash, timeout=10
|
|
3950
3961
|
)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
import asyncio
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
2
5
|
from typing import (
|
|
3
6
|
TYPE_CHECKING,
|
|
4
7
|
Any,
|
|
@@ -43,6 +46,9 @@ if TYPE_CHECKING:
|
|
|
43
46
|
)
|
|
44
47
|
|
|
45
48
|
|
|
49
|
+
SOME_BLOCK_KEYS = {"number", "hash", "parentHash", "stateRoot", "transactions"}
|
|
50
|
+
|
|
51
|
+
|
|
46
52
|
class Web3ModuleTest:
|
|
47
53
|
def test_web3_client_version(self, w3: Web3) -> None:
|
|
48
54
|
client_version = w3.client_version
|
|
@@ -336,7 +342,7 @@ class Web3ModuleTest:
|
|
|
336
342
|
|
|
337
343
|
# assert proper batch cleanup after execution
|
|
338
344
|
assert batch._requests_info == []
|
|
339
|
-
assert not
|
|
345
|
+
assert not w3.provider._is_batching
|
|
340
346
|
|
|
341
347
|
# assert batch cannot be added to after execution
|
|
342
348
|
with pytest.raises(
|
|
@@ -395,7 +401,7 @@ class Web3ModuleTest:
|
|
|
395
401
|
|
|
396
402
|
# assert proper batch cleanup after execution
|
|
397
403
|
assert batch._requests_info == []
|
|
398
|
-
assert not
|
|
404
|
+
assert not w3.provider._is_batching
|
|
399
405
|
|
|
400
406
|
# assert batch cannot be added to after execution
|
|
401
407
|
with pytest.raises(
|
|
@@ -513,6 +519,43 @@ class Web3ModuleTest:
|
|
|
513
519
|
batch.add(w3.eth.sign(Address(b"\x00" * 20)))
|
|
514
520
|
batch.execute()
|
|
515
521
|
|
|
522
|
+
def test_batch_requests_concurrently_with_regular_requests(
|
|
523
|
+
self, w3: "Web3"
|
|
524
|
+
) -> None:
|
|
525
|
+
num_requests = 40
|
|
526
|
+
responses = []
|
|
527
|
+
batch_response = []
|
|
528
|
+
|
|
529
|
+
def make_regular_requests() -> None:
|
|
530
|
+
for _ in range(num_requests):
|
|
531
|
+
responses.append(w3.eth.get_block(0))
|
|
532
|
+
time.sleep(0.01)
|
|
533
|
+
|
|
534
|
+
def make_batch_request() -> None:
|
|
535
|
+
with w3.batch_requests() as batch:
|
|
536
|
+
for _ in range(num_requests):
|
|
537
|
+
batch.add(w3.eth.get_block(0))
|
|
538
|
+
time.sleep(0.01)
|
|
539
|
+
batch_response.extend(batch.execute())
|
|
540
|
+
|
|
541
|
+
# split into threads
|
|
542
|
+
regular_thread = threading.Thread(target=make_regular_requests)
|
|
543
|
+
batch_thread = threading.Thread(target=make_batch_request)
|
|
544
|
+
|
|
545
|
+
regular_thread.start()
|
|
546
|
+
batch_thread.start()
|
|
547
|
+
|
|
548
|
+
# wait for threads to finish
|
|
549
|
+
regular_thread.join()
|
|
550
|
+
batch_thread.join()
|
|
551
|
+
assert not regular_thread.is_alive()
|
|
552
|
+
assert not batch_thread.is_alive()
|
|
553
|
+
|
|
554
|
+
assert len(responses) == num_requests
|
|
555
|
+
assert len(batch_response) == num_requests
|
|
556
|
+
assert all(SOME_BLOCK_KEYS.issubset(response.keys()) for response in responses)
|
|
557
|
+
assert set(responses) == set(batch_response)
|
|
558
|
+
|
|
516
559
|
|
|
517
560
|
# -- async -- #
|
|
518
561
|
|
|
@@ -551,7 +594,7 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
|
|
|
551
594
|
|
|
552
595
|
# assert proper batch cleanup after execution
|
|
553
596
|
assert batch._async_requests_info == []
|
|
554
|
-
assert not
|
|
597
|
+
assert not async_w3.provider._is_batching
|
|
555
598
|
|
|
556
599
|
# assert batch cannot be added to after execution
|
|
557
600
|
with pytest.raises(
|
|
@@ -614,7 +657,7 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
|
|
|
614
657
|
|
|
615
658
|
# assert proper batch cleanup after execution
|
|
616
659
|
assert batch._async_requests_info == []
|
|
617
|
-
assert not
|
|
660
|
+
assert not async_w3.provider._is_batching
|
|
618
661
|
|
|
619
662
|
# assert batch cannot be added to after execution
|
|
620
663
|
with pytest.raises(
|
|
@@ -734,3 +777,34 @@ class AsyncWeb3ModuleTest(Web3ModuleTest):
|
|
|
734
777
|
with pytest.raises(MethodNotSupported, match="eth_sign"):
|
|
735
778
|
batch.add(async_w3.eth.sign(Address(b"\x00" * 20)))
|
|
736
779
|
await batch.async_execute()
|
|
780
|
+
|
|
781
|
+
@pytest.mark.asyncio
|
|
782
|
+
async def test_batch_requests_concurrently_with_regular_requests( # type: ignore[override] # noqa: E501
|
|
783
|
+
self, async_w3: AsyncWeb3 # type: ignore[override]
|
|
784
|
+
) -> None:
|
|
785
|
+
responses = []
|
|
786
|
+
batch_response = []
|
|
787
|
+
|
|
788
|
+
num_blocks = await async_w3.eth.block_number
|
|
789
|
+
|
|
790
|
+
async def make_regular_requests() -> None:
|
|
791
|
+
for i in range(num_blocks):
|
|
792
|
+
responses.append(await async_w3.eth.get_block(i))
|
|
793
|
+
await asyncio.sleep(0.01)
|
|
794
|
+
|
|
795
|
+
async def make_batch_request() -> None:
|
|
796
|
+
async with async_w3.batch_requests() as batch:
|
|
797
|
+
for i in range(num_blocks):
|
|
798
|
+
batch.add(async_w3.eth.get_block(i))
|
|
799
|
+
await asyncio.sleep(0.01)
|
|
800
|
+
batch_response.extend(await batch.async_execute())
|
|
801
|
+
|
|
802
|
+
await asyncio.gather(
|
|
803
|
+
make_regular_requests(),
|
|
804
|
+
make_batch_request(),
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
assert len(responses) == num_blocks
|
|
808
|
+
assert len(batch_response) == num_blocks
|
|
809
|
+
assert all(SOME_BLOCK_KEYS.issubset(response.keys()) for response in responses)
|
|
810
|
+
assert set(responses) == set(batch_response)
|
web3/_utils/validation.py
CHANGED
|
@@ -396,7 +396,7 @@ def validate_rpc_response_and_raise_if_error(
|
|
|
396
396
|
|
|
397
397
|
response = apply_error_formatters(error_formatters, response)
|
|
398
398
|
if logger is not None:
|
|
399
|
-
logger.debug(
|
|
399
|
+
logger.debug("RPC error response: %s", response)
|
|
400
400
|
|
|
401
401
|
raise web3_rpc_error
|
|
402
402
|
|
web3/contract/utils.py
CHANGED
|
@@ -43,8 +43,8 @@ from web3._utils.abi import (
|
|
|
43
43
|
from web3._utils.async_transactions import (
|
|
44
44
|
async_fill_transaction_defaults,
|
|
45
45
|
)
|
|
46
|
-
from web3._utils.
|
|
47
|
-
|
|
46
|
+
from web3._utils.batching import (
|
|
47
|
+
BatchRequestInformation,
|
|
48
48
|
)
|
|
49
49
|
from web3._utils.contracts import (
|
|
50
50
|
prepare_transaction,
|
|
@@ -62,7 +62,6 @@ from web3.exceptions import (
|
|
|
62
62
|
from web3.types import (
|
|
63
63
|
ABIElementIdentifier,
|
|
64
64
|
BlockIdentifier,
|
|
65
|
-
RPCEndpoint,
|
|
66
65
|
StateOverride,
|
|
67
66
|
TContractEvent,
|
|
68
67
|
TContractFn,
|
|
@@ -175,10 +174,8 @@ def call_contract_function(
|
|
|
175
174
|
if abi_callable["type"] == "function":
|
|
176
175
|
output_types = get_abi_output_types(abi_callable)
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
BatchingReturnData: TypeAlias = Tuple[Tuple[RPCEndpoint, Any], Tuple[Any, ...]]
|
|
181
|
-
request_information = tuple(cast(BatchingReturnData, return_data))
|
|
177
|
+
if w3.provider._is_batching:
|
|
178
|
+
request_information = tuple(cast(BatchRequestInformation, return_data))
|
|
182
179
|
method_and_params = request_information[0]
|
|
183
180
|
|
|
184
181
|
# append return data formatting to result formatters
|
|
@@ -483,35 +480,23 @@ async def async_call_contract_function(
|
|
|
483
480
|
normalizers,
|
|
484
481
|
output_types,
|
|
485
482
|
)
|
|
486
|
-
if async_w3.provider.has_persistent_connection:
|
|
487
|
-
# get the current request id
|
|
488
|
-
provider = cast("PersistentConnectionProvider", async_w3.provider)
|
|
489
|
-
current_request_id = provider._batch_request_counter - 1
|
|
490
|
-
provider._request_processor.append_result_formatter_for_request(
|
|
491
|
-
current_request_id, contract_call_return_data_formatter
|
|
492
|
-
)
|
|
493
|
-
else:
|
|
494
|
-
BatchingReturnData: TypeAlias = Tuple[
|
|
495
|
-
Tuple[RPCEndpoint, Any], Tuple[Any, ...]
|
|
496
|
-
]
|
|
497
|
-
request_information = tuple(cast(BatchingReturnData, return_data))
|
|
498
|
-
method_and_params = request_information[0]
|
|
499
|
-
|
|
500
|
-
# append return data formatter to result formatters
|
|
501
|
-
current_response_formatters = request_information[1]
|
|
502
|
-
current_result_formatters = current_response_formatters[0]
|
|
503
|
-
updated_result_formatters = compose(
|
|
504
|
-
contract_call_return_data_formatter,
|
|
505
|
-
current_result_formatters,
|
|
506
|
-
)
|
|
507
|
-
response_formatters = (
|
|
508
|
-
updated_result_formatters, # result formatters
|
|
509
|
-
current_response_formatters[1], # error formatters
|
|
510
|
-
current_response_formatters[2], # null result formatters
|
|
511
|
-
)
|
|
512
|
-
return (method_and_params, response_formatters)
|
|
513
483
|
|
|
514
|
-
|
|
484
|
+
request_information = tuple(cast(BatchRequestInformation, return_data))
|
|
485
|
+
method_and_params = request_information[0]
|
|
486
|
+
|
|
487
|
+
# append return data formatter to result formatters
|
|
488
|
+
current_response_formatters = request_information[1]
|
|
489
|
+
current_result_formatters = current_response_formatters[0]
|
|
490
|
+
updated_result_formatters = compose(
|
|
491
|
+
contract_call_return_data_formatter,
|
|
492
|
+
current_result_formatters,
|
|
493
|
+
)
|
|
494
|
+
response_formatters = (
|
|
495
|
+
updated_result_formatters, # result formatters
|
|
496
|
+
current_response_formatters[1], # error formatters
|
|
497
|
+
current_response_formatters[2], # null result formatters
|
|
498
|
+
)
|
|
499
|
+
return (method_and_params, response_formatters)
|
|
515
500
|
|
|
516
501
|
try:
|
|
517
502
|
output_data = async_w3.codec.decode(output_types, return_data)
|
web3/manager.py
CHANGED
|
@@ -159,7 +159,7 @@ class RequestManager:
|
|
|
159
159
|
request_func = provider.request_func(
|
|
160
160
|
cast("Web3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
161
161
|
)
|
|
162
|
-
self.logger.debug(
|
|
162
|
+
self.logger.debug("Making request. Method: %s", method)
|
|
163
163
|
return request_func(method, params)
|
|
164
164
|
|
|
165
165
|
async def _coro_make_request(
|
|
@@ -169,7 +169,7 @@ class RequestManager:
|
|
|
169
169
|
request_func = await provider.request_func(
|
|
170
170
|
cast("AsyncWeb3", self.w3), cast("MiddlewareOnion", self.middleware_onion)
|
|
171
171
|
)
|
|
172
|
-
self.logger.debug(
|
|
172
|
+
self.logger.debug("Making request. Method: %s", method)
|
|
173
173
|
return await request_func(method, params)
|
|
174
174
|
|
|
175
175
|
#
|
|
@@ -261,7 +261,7 @@ class RequestManager:
|
|
|
261
261
|
return RequestBatcher(self.w3)
|
|
262
262
|
|
|
263
263
|
def _make_batch_request(
|
|
264
|
-
self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any],
|
|
264
|
+
self, requests_info: List[Tuple[Tuple["RPCEndpoint", Any], Tuple[Any, ...]]]
|
|
265
265
|
) -> List[RPCResponse]:
|
|
266
266
|
"""
|
|
267
267
|
Make a batch request using the provider
|
|
@@ -291,7 +291,7 @@ class RequestManager:
|
|
|
291
291
|
async def _async_make_batch_request(
|
|
292
292
|
self,
|
|
293
293
|
requests_info: List[
|
|
294
|
-
Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any],
|
|
294
|
+
Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Tuple[Any]]]
|
|
295
295
|
],
|
|
296
296
|
) -> List[RPCResponse]:
|
|
297
297
|
"""
|
|
@@ -315,13 +315,6 @@ class RequestManager:
|
|
|
315
315
|
if isinstance(response, list):
|
|
316
316
|
# expected format
|
|
317
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
318
|
formatted_responses = [
|
|
326
319
|
self._format_batched_response(info, resp)
|
|
327
320
|
for info, resp in zip(unpacked_requests_info, response)
|
|
@@ -331,6 +324,86 @@ class RequestManager:
|
|
|
331
324
|
# expect a single response with an error
|
|
332
325
|
raise_error_for_batch_response(response, self.logger)
|
|
333
326
|
|
|
327
|
+
async def _async_send_batch(
|
|
328
|
+
self, requests: List[Tuple["RPCEndpoint", Any]]
|
|
329
|
+
) -> List[RPCRequest]:
|
|
330
|
+
"""
|
|
331
|
+
Send a batch request via socket.
|
|
332
|
+
"""
|
|
333
|
+
if not isinstance(self._provider, PersistentConnectionProvider):
|
|
334
|
+
raise Web3TypeError(
|
|
335
|
+
"Only providers that maintain an open, persistent connection "
|
|
336
|
+
"can send batch requests."
|
|
337
|
+
)
|
|
338
|
+
send_func = await self._provider.send_batch_func(
|
|
339
|
+
cast("AsyncWeb3", self.w3),
|
|
340
|
+
cast("MiddlewareOnion", self.middleware_onion),
|
|
341
|
+
)
|
|
342
|
+
self.logger.debug(
|
|
343
|
+
"Sending batch request to open socket connection: %s",
|
|
344
|
+
self._provider.get_endpoint_uri_or_ipc_path(),
|
|
345
|
+
)
|
|
346
|
+
return await send_func(requests)
|
|
347
|
+
|
|
348
|
+
async def _async_recv_batch(self, requests: List[RPCRequest]) -> List[RPCResponse]:
|
|
349
|
+
"""
|
|
350
|
+
Receive a batch request via socket.
|
|
351
|
+
"""
|
|
352
|
+
if not isinstance(self._provider, PersistentConnectionProvider):
|
|
353
|
+
raise Web3TypeError(
|
|
354
|
+
"Only providers that maintain an open, persistent connection "
|
|
355
|
+
"can receive batch requests."
|
|
356
|
+
)
|
|
357
|
+
recv_func = await self._provider.recv_batch_func(
|
|
358
|
+
cast("AsyncWeb3", self.w3),
|
|
359
|
+
cast("MiddlewareOnion", self.middleware_onion),
|
|
360
|
+
)
|
|
361
|
+
self.logger.debug(
|
|
362
|
+
"Receiving batch request from open socket connection: %s",
|
|
363
|
+
self._provider.get_endpoint_uri_or_ipc_path(),
|
|
364
|
+
)
|
|
365
|
+
return await recv_func(requests)
|
|
366
|
+
|
|
367
|
+
async def _async_make_socket_batch_request(
|
|
368
|
+
self,
|
|
369
|
+
requests_info: List[
|
|
370
|
+
Coroutine[Any, Any, Tuple[Tuple["RPCEndpoint", Any], Tuple[Any, ...]]]
|
|
371
|
+
],
|
|
372
|
+
) -> List[RPCResponse]:
|
|
373
|
+
"""
|
|
374
|
+
Send and receive a batch request via a socket.
|
|
375
|
+
"""
|
|
376
|
+
if not isinstance(self._provider, PersistentConnectionProvider):
|
|
377
|
+
raise Web3TypeError(
|
|
378
|
+
"Only providers that maintain an open, persistent connection "
|
|
379
|
+
"can send and receive batch requests."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
unpacked_requests_info = await asyncio.gather(*requests_info)
|
|
383
|
+
reqs = [req for req, _ in unpacked_requests_info]
|
|
384
|
+
response_formatters = [resp_f for _, resp_f in unpacked_requests_info]
|
|
385
|
+
|
|
386
|
+
requests = await self._async_send_batch(reqs)
|
|
387
|
+
|
|
388
|
+
for i, request in enumerate(requests):
|
|
389
|
+
self._provider._request_processor.cache_request_information(
|
|
390
|
+
request["id"],
|
|
391
|
+
request["method"],
|
|
392
|
+
request["params"],
|
|
393
|
+
response_formatters=response_formatters[i],
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
responses = await self._async_recv_batch(requests)
|
|
397
|
+
if isinstance(responses, list):
|
|
398
|
+
# expected format
|
|
399
|
+
return [
|
|
400
|
+
cast(RPCResponse, await self._process_response(resp))
|
|
401
|
+
for resp in responses
|
|
402
|
+
]
|
|
403
|
+
else:
|
|
404
|
+
# expect a single response with an error
|
|
405
|
+
raise_error_for_batch_response(responses, self.logger)
|
|
406
|
+
|
|
334
407
|
def _format_batched_response(
|
|
335
408
|
self,
|
|
336
409
|
requests_info: Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]],
|
|
@@ -366,9 +439,12 @@ class RequestManager:
|
|
|
366
439
|
) -> RPCResponse:
|
|
367
440
|
provider = cast(PersistentConnectionProvider, self._provider)
|
|
368
441
|
self.logger.debug(
|
|
369
|
-
"Making request to open socket connection and waiting for response: "
|
|
370
|
-
|
|
371
|
-
|
|
442
|
+
"Making request to open socket connection and waiting for response: %s,\n"
|
|
443
|
+
" method: %s,\n"
|
|
444
|
+
" params: %s",
|
|
445
|
+
provider.get_endpoint_uri_or_ipc_path(),
|
|
446
|
+
method,
|
|
447
|
+
params,
|
|
372
448
|
)
|
|
373
449
|
rpc_request = await self.send(method, params)
|
|
374
450
|
provider._request_processor.cache_request_information(
|
|
@@ -388,9 +464,12 @@ class RequestManager:
|
|
|
388
464
|
middleware_onion,
|
|
389
465
|
)
|
|
390
466
|
self.logger.debug(
|
|
391
|
-
"Sending request to open socket connection: "
|
|
392
|
-
|
|
393
|
-
|
|
467
|
+
"Sending request to open socket connection: %s,\n"
|
|
468
|
+
" method: %s,\n"
|
|
469
|
+
" params: %s",
|
|
470
|
+
provider.get_endpoint_uri_or_ipc_path(),
|
|
471
|
+
method,
|
|
472
|
+
params,
|
|
394
473
|
)
|
|
395
474
|
return await send_func(method, params)
|
|
396
475
|
|
|
@@ -404,7 +483,8 @@ class RequestManager:
|
|
|
404
483
|
)
|
|
405
484
|
self.logger.debug(
|
|
406
485
|
"Getting response for request from open socket connection:\n"
|
|
407
|
-
|
|
486
|
+
" request: %s",
|
|
487
|
+
rpc_request,
|
|
408
488
|
)
|
|
409
489
|
response = await recv_func(rpc_request)
|
|
410
490
|
try:
|
|
@@ -417,8 +497,8 @@ class RequestManager:
|
|
|
417
497
|
async def recv(self) -> Union[RPCResponse, FormattedEthSubscriptionResponse]:
|
|
418
498
|
provider = cast(PersistentConnectionProvider, self._provider)
|
|
419
499
|
self.logger.debug(
|
|
420
|
-
"Getting next response from open socket connection: "
|
|
421
|
-
|
|
500
|
+
"Getting next response from open socket connection: %s",
|
|
501
|
+
provider.get_endpoint_uri_or_ipc_path(),
|
|
422
502
|
)
|
|
423
503
|
# pop from the queue since the listener task is responsible for reading
|
|
424
504
|
# directly from the socket
|
|
@@ -501,9 +581,11 @@ class RequestManager:
|
|
|
501
581
|
# subscription as it comes in
|
|
502
582
|
request_info.subscription_id = subscription_id
|
|
503
583
|
provider.logger.debug(
|
|
504
|
-
"Caching eth_subscription info:\n
|
|
505
|
-
|
|
506
|
-
|
|
584
|
+
"Caching eth_subscription info:\n"
|
|
585
|
+
" cache_key=%s,\n"
|
|
586
|
+
" request_info=%s",
|
|
587
|
+
cache_key,
|
|
588
|
+
request_info.__dict__,
|
|
507
589
|
)
|
|
508
590
|
self._request_processor._request_information_cache.cache(
|
|
509
591
|
cache_key, request_info
|
web3/method.py
CHANGED
|
@@ -165,8 +165,7 @@ class Method(Generic[TFunc]):
|
|
|
165
165
|
"usually attached to a web3 instance."
|
|
166
166
|
)
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
if hasattr(provider, "_is_batching") and provider._is_batching:
|
|
168
|
+
if module.w3.provider._is_batching:
|
|
170
169
|
if self.json_rpc_method in RPC_METHODS_UNSUPPORTED_DURING_BATCH:
|
|
171
170
|
raise MethodNotSupported(
|
|
172
171
|
f"Method `{self.json_rpc_method}` is not supported within a batch "
|
|
@@ -182,12 +181,13 @@ class Method(Generic[TFunc]):
|
|
|
182
181
|
@property
|
|
183
182
|
def method_selector_fn(
|
|
184
183
|
self,
|
|
185
|
-
) -> Callable[
|
|
184
|
+
) -> Callable[[], RPCEndpoint]:
|
|
186
185
|
"""Gets the method selector from the config."""
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
method = self.json_rpc_method
|
|
187
|
+
if callable(method):
|
|
188
|
+
return method
|
|
189
|
+
elif isinstance(method, str):
|
|
190
|
+
return lambda: method
|
|
191
191
|
raise Web3ValueError(
|
|
192
192
|
"``json_rpc_method`` config invalid. May be a string or function"
|
|
193
193
|
)
|
web3/providers/async_base.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextvars
|
|
2
3
|
import itertools
|
|
3
4
|
import logging
|
|
4
5
|
from typing import (
|
|
@@ -61,6 +62,9 @@ if TYPE_CHECKING:
|
|
|
61
62
|
AsyncWeb3,
|
|
62
63
|
WebSocketProvider,
|
|
63
64
|
)
|
|
65
|
+
from web3._utils.batching import ( # noqa: F401
|
|
66
|
+
RequestBatcher,
|
|
67
|
+
)
|
|
64
68
|
from web3.providers.persistent import ( # noqa: F401
|
|
65
69
|
RequestProcessor,
|
|
66
70
|
)
|
|
@@ -94,12 +98,19 @@ class AsyncBaseProvider:
|
|
|
94
98
|
self.cache_allowed_requests = cache_allowed_requests
|
|
95
99
|
self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
|
|
96
100
|
self.request_cache_validation_threshold = request_cache_validation_threshold
|
|
97
|
-
|
|
101
|
+
|
|
102
|
+
self._batching_context: contextvars.ContextVar[
|
|
103
|
+
Optional["RequestBatcher[Any]"]
|
|
104
|
+
] = contextvars.ContextVar("batching_context", default=None)
|
|
98
105
|
self._batch_request_func_cache: Tuple[
|
|
99
106
|
Tuple[Middleware, ...],
|
|
100
107
|
Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]],
|
|
101
108
|
] = (None, None)
|
|
102
109
|
|
|
110
|
+
@property
|
|
111
|
+
def _is_batching(self) -> bool:
|
|
112
|
+
return self._batching_context.get() is not None
|
|
113
|
+
|
|
103
114
|
async def request_func(
|
|
104
115
|
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
105
116
|
) -> Callable[..., Coroutine[Any, Any, RPCResponse]]:
|
|
@@ -240,3 +251,6 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
|
|
|
240
251
|
)
|
|
241
252
|
+ b"]"
|
|
242
253
|
)
|
|
254
|
+
|
|
255
|
+
def encode_batch_request_dicts(self, request_dicts: List[RPCRequest]) -> bytes:
|
|
256
|
+
return b"[" + b",".join(self.encode_rpc_dict(d) for d in request_dicts) + b"]"
|
web3/providers/base.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextvars
|
|
1
2
|
import itertools
|
|
2
3
|
import logging
|
|
3
4
|
import threading
|
|
@@ -50,6 +51,9 @@ from web3.utils import (
|
|
|
50
51
|
|
|
51
52
|
if TYPE_CHECKING:
|
|
52
53
|
from web3 import Web3 # noqa: F401
|
|
54
|
+
from web3._utils.batching import (
|
|
55
|
+
RequestBatcher,
|
|
56
|
+
)
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
class BaseProvider:
|
|
@@ -81,6 +85,20 @@ class BaseProvider:
|
|
|
81
85
|
self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
|
|
82
86
|
self.request_cache_validation_threshold = request_cache_validation_threshold
|
|
83
87
|
|
|
88
|
+
self._batching_context: contextvars.ContextVar[
|
|
89
|
+
Optional["RequestBatcher[Any]"]
|
|
90
|
+
] = contextvars.ContextVar("batching_context", default=None)
|
|
91
|
+
self._batch_request_func_cache: Tuple[
|
|
92
|
+
Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
|
|
93
|
+
] = (None, None)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def _is_batching(self) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Check if the provider is currently batching requests.
|
|
99
|
+
"""
|
|
100
|
+
return self._batching_context.get() is not None
|
|
101
|
+
|
|
84
102
|
def request_func(
|
|
85
103
|
self, w3: "Web3", middleware_onion: MiddlewareOnion
|
|
86
104
|
) -> Callable[..., RPCResponse]:
|
|
@@ -120,11 +138,6 @@ class JSONBaseProvider(BaseProvider):
|
|
|
120
138
|
super().__init__(**kwargs)
|
|
121
139
|
self.request_counter = itertools.count()
|
|
122
140
|
|
|
123
|
-
self._is_batching: bool = False
|
|
124
|
-
self._batch_request_func_cache: Tuple[
|
|
125
|
-
Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
|
|
126
|
-
] = (None, None)
|
|
127
|
-
|
|
128
141
|
def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
|
|
129
142
|
rpc_dict = {
|
|
130
143
|
"jsonrpc": "2.0",
|
web3/providers/ipc.py
CHANGED
|
@@ -30,7 +30,6 @@ from web3.types import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
from .._utils.batching import (
|
|
33
|
-
batching_context,
|
|
34
33
|
sort_batch_response_by_response_ids,
|
|
35
34
|
)
|
|
36
35
|
from .._utils.caching import (
|
|
@@ -197,16 +196,15 @@ class IPCProvider(JSONBaseProvider):
|
|
|
197
196
|
@handle_request_caching
|
|
198
197
|
def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
199
198
|
self.logger.debug(
|
|
200
|
-
|
|
199
|
+
"Making request IPC. Path: %s, Method: %s", self.ipc_path, method
|
|
201
200
|
)
|
|
202
201
|
request = self.encode_rpc_request(method, params)
|
|
203
202
|
return self._make_request(request)
|
|
204
203
|
|
|
205
|
-
@batching_context
|
|
206
204
|
def make_batch_request(
|
|
207
205
|
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
208
206
|
) -> List[RPCResponse]:
|
|
209
|
-
self.logger.debug(
|
|
207
|
+
self.logger.debug("Making batch request IPC. Path: %s", self.ipc_path)
|
|
210
208
|
request_data = self.encode_batch_rpc_request(requests)
|
|
211
209
|
response = cast(List[RPCResponse], self._make_request(request_data))
|
|
212
210
|
return sort_batch_response_by_response_ids(response)
|