web3 7.12.0__py3-none-any.whl → 7.13.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.
- web3/_utils/module_testing/eth_module.py +14 -40
- web3/eth/async_eth.py +2 -13
- web3/exceptions.py +8 -1
- web3/manager.py +5 -8
- web3/middleware/formatting.py +7 -1
- web3/providers/auto.py +28 -6
- web3/providers/ipc.py +2 -2
- web3/providers/persistent/persistent.py +4 -3
- web3/providers/persistent/subscription_manager.py +66 -13
- web3/utils/subscriptions.py +23 -2
- {web3-7.12.0.dist-info → web3-7.13.0.dist-info}/METADATA +1 -1
- {web3-7.12.0.dist-info → web3-7.13.0.dist-info}/RECORD +15 -15
- {web3-7.12.0.dist-info → web3-7.13.0.dist-info}/WHEEL +1 -1
- {web3-7.12.0.dist-info → web3-7.13.0.dist-info}/licenses/LICENSE +0 -0
- {web3-7.12.0.dist-info → web3-7.13.0.dist-info}/top_level.txt +0 -0
|
@@ -724,8 +724,6 @@ class AsyncEthModuleTest:
|
|
|
724
724
|
keyfile_account_pkey: HexStr,
|
|
725
725
|
async_math_contract: "AsyncContract",
|
|
726
726
|
) -> None:
|
|
727
|
-
# TODO: remove blockNumber block_id from eth_call and eth_getCode calls once
|
|
728
|
-
# geth behavior for "latest" seems stable again.
|
|
729
727
|
keyfile_account = async_w3.eth.account.from_key(keyfile_account_pkey)
|
|
730
728
|
|
|
731
729
|
chain_id = await async_w3.eth.chain_id
|
|
@@ -757,13 +755,9 @@ class AsyncEthModuleTest:
|
|
|
757
755
|
signed = keyfile_account.sign_transaction(txn)
|
|
758
756
|
tx_hash = await async_w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
759
757
|
get_tx = await async_w3.eth.get_transaction(tx_hash)
|
|
760
|
-
|
|
761
|
-
tx_hash, timeout=10
|
|
762
|
-
)
|
|
758
|
+
await async_w3.eth.wait_for_transaction_receipt(tx_hash)
|
|
763
759
|
|
|
764
|
-
code = await async_w3.eth.get_code(
|
|
765
|
-
keyfile_account.address, block_identifier=tx_receipt["blockNumber"]
|
|
766
|
-
)
|
|
760
|
+
code = await async_w3.eth.get_code(keyfile_account.address)
|
|
767
761
|
assert code.to_0x_hex() == f"0xef0100{async_math_contract.address[2:].lower()}"
|
|
768
762
|
delegated = async_w3.eth.contract(
|
|
769
763
|
address=keyfile_account.address, abi=async_math_contract.abi
|
|
@@ -772,7 +766,7 @@ class AsyncEthModuleTest:
|
|
|
772
766
|
# assert the math counter is increased by 1337 only in delegated acct
|
|
773
767
|
assert await async_math_contract.functions.counter().call() == math_counter
|
|
774
768
|
delegated_call = await delegated.functions.counter().call(
|
|
775
|
-
block_identifier=
|
|
769
|
+
block_identifier="latest"
|
|
776
770
|
)
|
|
777
771
|
assert delegated_call == math_counter + 1337
|
|
778
772
|
|
|
@@ -803,13 +797,9 @@ class AsyncEthModuleTest:
|
|
|
803
797
|
|
|
804
798
|
# test eth_sendTransaction
|
|
805
799
|
reset_tx_hash = await async_w3.eth.send_transaction(reset_code_txn)
|
|
806
|
-
|
|
807
|
-
reset_tx_hash, timeout=10
|
|
808
|
-
)
|
|
800
|
+
await async_w3.eth.wait_for_transaction_receipt(reset_tx_hash, timeout=10)
|
|
809
801
|
|
|
810
|
-
reset_code = await async_w3.eth.get_code(
|
|
811
|
-
keyfile_account.address, reset_tx_receipt["blockNumber"]
|
|
812
|
-
)
|
|
802
|
+
reset_code = await async_w3.eth.get_code(keyfile_account.address)
|
|
813
803
|
assert reset_code == HexBytes("0x")
|
|
814
804
|
|
|
815
805
|
@pytest.mark.asyncio
|
|
@@ -3875,8 +3865,6 @@ class EthModuleTest:
|
|
|
3875
3865
|
def test_sign_authorization_send_raw_and_send_set_code_transactions(
|
|
3876
3866
|
self, w3: "Web3", keyfile_account_pkey: HexStr, math_contract: "Contract"
|
|
3877
3867
|
) -> None:
|
|
3878
|
-
# TODO: remove blockNumber block_id from eth_call and eth_getCode calls once
|
|
3879
|
-
# geth behavior for "latest" seems stable again.
|
|
3880
3868
|
keyfile_account = w3.eth.account.from_key(keyfile_account_pkey)
|
|
3881
3869
|
|
|
3882
3870
|
chain_id = w3.eth.chain_id
|
|
@@ -3906,28 +3894,18 @@ class EthModuleTest:
|
|
|
3906
3894
|
|
|
3907
3895
|
# test eth_sendRawTransaction
|
|
3908
3896
|
signed = keyfile_account.sign_transaction(txn)
|
|
3909
|
-
|
|
3910
|
-
get_tx = w3.eth.get_transaction(
|
|
3911
|
-
|
|
3897
|
+
w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
3898
|
+
get_tx = w3.eth.get_transaction(signed.hash)
|
|
3899
|
+
w3.eth.wait_for_transaction_receipt(signed.hash)
|
|
3912
3900
|
|
|
3913
|
-
code = w3.eth.get_code(
|
|
3914
|
-
keyfile_account.address, block_identifier=receipt["blockNumber"]
|
|
3915
|
-
)
|
|
3901
|
+
code = w3.eth.get_code(keyfile_account.address)
|
|
3916
3902
|
assert code.to_0x_hex() == f"0xef0100{math_contract.address[2:].lower()}"
|
|
3917
3903
|
delegated = w3.eth.contract(
|
|
3918
3904
|
address=keyfile_account.address, abi=math_contract.abi
|
|
3919
3905
|
)
|
|
3920
3906
|
# assert the math counter is increased by 1337 only in delegated acct
|
|
3921
|
-
assert (
|
|
3922
|
-
|
|
3923
|
-
block_identifier=receipt["blockNumber"]
|
|
3924
|
-
)
|
|
3925
|
-
== math_counter
|
|
3926
|
-
)
|
|
3927
|
-
assert (
|
|
3928
|
-
delegated.functions.counter().call(block_identifier=receipt["blockNumber"])
|
|
3929
|
-
== math_counter + 1337
|
|
3930
|
-
)
|
|
3907
|
+
assert math_contract.functions.counter().call() == math_counter
|
|
3908
|
+
assert delegated.functions.counter().call() == math_counter + 1337
|
|
3931
3909
|
|
|
3932
3910
|
assert len(get_tx["authorizationList"]) == 1
|
|
3933
3911
|
get_auth = get_tx["authorizationList"][0]
|
|
@@ -3938,7 +3916,7 @@ class EthModuleTest:
|
|
|
3938
3916
|
assert isinstance(get_auth["r"], HexBytes)
|
|
3939
3917
|
assert isinstance(get_auth["s"], HexBytes)
|
|
3940
3918
|
|
|
3941
|
-
# reset
|
|
3919
|
+
# reset code
|
|
3942
3920
|
reset_auth = {
|
|
3943
3921
|
"chainId": chain_id,
|
|
3944
3922
|
"address": "0x" + ("00" * 20),
|
|
@@ -3956,13 +3934,9 @@ class EthModuleTest:
|
|
|
3956
3934
|
|
|
3957
3935
|
# test eth_sendTransaction
|
|
3958
3936
|
reset_tx_hash = w3.eth.send_transaction(reset_code_txn)
|
|
3959
|
-
|
|
3960
|
-
reset_tx_hash, timeout=10
|
|
3961
|
-
)
|
|
3937
|
+
w3.eth.wait_for_transaction_receipt(reset_tx_hash)
|
|
3962
3938
|
|
|
3963
|
-
reset_code = w3.eth.get_code(
|
|
3964
|
-
keyfile_account.address, block_identifier=reset_tx_receipt["blockNumber"]
|
|
3965
|
-
)
|
|
3939
|
+
reset_code = w3.eth.get_code(keyfile_account.address)
|
|
3966
3940
|
assert reset_code == HexBytes("0x")
|
|
3967
3941
|
|
|
3968
3942
|
def test_eth_call(self, w3: "Web3", math_contract: "Contract") -> None:
|
web3/eth/async_eth.py
CHANGED
|
@@ -720,19 +720,6 @@ class AsyncEth(BaseEth):
|
|
|
720
720
|
mungers=[default_root_munger],
|
|
721
721
|
)
|
|
722
722
|
|
|
723
|
-
_subscribe_with_args: Method[
|
|
724
|
-
Callable[
|
|
725
|
-
[
|
|
726
|
-
SubscriptionType,
|
|
727
|
-
Optional[Union[LogsSubscriptionArg, bool]],
|
|
728
|
-
],
|
|
729
|
-
Awaitable[HexStr],
|
|
730
|
-
]
|
|
731
|
-
] = Method(
|
|
732
|
-
RPC.eth_subscribe,
|
|
733
|
-
mungers=[default_root_munger],
|
|
734
|
-
)
|
|
735
|
-
|
|
736
723
|
async def subscribe(
|
|
737
724
|
self,
|
|
738
725
|
subscription_type: SubscriptionType,
|
|
@@ -745,6 +732,7 @@ class AsyncEth(BaseEth):
|
|
|
745
732
|
handler: Optional[EthSubscriptionHandler] = None,
|
|
746
733
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
747
734
|
label: Optional[str] = None,
|
|
735
|
+
parallelize: Optional[bool] = None,
|
|
748
736
|
) -> HexStr:
|
|
749
737
|
if not isinstance(self.w3.provider, PersistentConnectionProvider):
|
|
750
738
|
raise MethodNotSupported(
|
|
@@ -757,6 +745,7 @@ class AsyncEth(BaseEth):
|
|
|
757
745
|
handler=handler,
|
|
758
746
|
handler_context=handler_context or {},
|
|
759
747
|
label=label,
|
|
748
|
+
parallelize=parallelize,
|
|
760
749
|
)
|
|
761
750
|
return await self.w3.subscription_manager.subscribe(sub)
|
|
762
751
|
|
web3/exceptions.py
CHANGED
|
@@ -353,13 +353,20 @@ class PersistentConnectionClosedOK(PersistentConnectionError):
|
|
|
353
353
|
"""
|
|
354
354
|
|
|
355
355
|
|
|
356
|
-
class SubscriptionProcessingFinished(
|
|
356
|
+
class SubscriptionProcessingFinished(PersistentConnectionError):
|
|
357
357
|
"""
|
|
358
358
|
Raised to alert the subscription manager that the processing of subscriptions
|
|
359
359
|
has finished.
|
|
360
360
|
"""
|
|
361
361
|
|
|
362
362
|
|
|
363
|
+
class SubscriptionHandlerTaskException(TaskNotRunning):
|
|
364
|
+
"""
|
|
365
|
+
Raised to alert the subscription manager that an exception occurred in the
|
|
366
|
+
subscription processing task.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
|
|
363
370
|
class Web3RPCError(Web3Exception):
|
|
364
371
|
"""
|
|
365
372
|
Raised when a JSON-RPC response contains an error field.
|
web3/manager.py
CHANGED
|
@@ -254,9 +254,9 @@ class RequestManager:
|
|
|
254
254
|
"""
|
|
255
255
|
Context manager for making batch requests
|
|
256
256
|
"""
|
|
257
|
-
if isinstance(
|
|
258
|
-
self.provider
|
|
259
|
-
|
|
257
|
+
if not isinstance(
|
|
258
|
+
self.provider, (AsyncJSONBaseProvider, JSONBaseProvider, AutoProvider)
|
|
259
|
+
):
|
|
260
260
|
raise Web3TypeError("Batch requests are not supported by this provider.")
|
|
261
261
|
return RequestBatcher(self.w3)
|
|
262
262
|
|
|
@@ -550,13 +550,10 @@ class RequestManager:
|
|
|
550
550
|
else:
|
|
551
551
|
# if not an active sub, skip processing and continue
|
|
552
552
|
continue
|
|
553
|
-
except TaskNotRunning:
|
|
553
|
+
except TaskNotRunning as e:
|
|
554
554
|
await asyncio.sleep(0)
|
|
555
555
|
self._provider._handle_listener_task_exceptions()
|
|
556
|
-
self.logger.error(
|
|
557
|
-
"Message listener background task has stopped unexpectedly. "
|
|
558
|
-
"Stopping message stream."
|
|
559
|
-
)
|
|
556
|
+
self.logger.error("Stopping message stream: %s", e.message)
|
|
560
557
|
return
|
|
561
558
|
|
|
562
559
|
async def _process_response(
|
web3/middleware/formatting.py
CHANGED
|
@@ -16,6 +16,7 @@ from eth_utils.toolz import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from web3.exceptions import (
|
|
19
|
+
BadResponseFormat,
|
|
19
20
|
Web3ValueError,
|
|
20
21
|
)
|
|
21
22
|
from web3.middleware.base import (
|
|
@@ -77,7 +78,12 @@ def _apply_response_formatters(
|
|
|
77
78
|
response, response_type, method_response_formatter(appropriate_response)
|
|
78
79
|
)
|
|
79
80
|
|
|
80
|
-
if
|
|
81
|
+
if not isinstance(response, dict):
|
|
82
|
+
raise BadResponseFormat(
|
|
83
|
+
"Malformed response: expected a valid JSON-RPC response object, got: "
|
|
84
|
+
"`{}`".format(response)
|
|
85
|
+
)
|
|
86
|
+
elif response.get("result") is not None and method in result_formatters:
|
|
81
87
|
return _format_response("result", result_formatters[method])
|
|
82
88
|
elif (
|
|
83
89
|
# eth_subscription responses
|
web3/providers/auto.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import (
|
|
|
3
3
|
Any,
|
|
4
4
|
Callable,
|
|
5
5
|
Dict,
|
|
6
|
+
List,
|
|
6
7
|
Optional,
|
|
7
8
|
Sequence,
|
|
8
9
|
Tuple,
|
|
@@ -21,9 +22,9 @@ from web3.exceptions import (
|
|
|
21
22
|
CannotHandleRequest,
|
|
22
23
|
)
|
|
23
24
|
from web3.providers import (
|
|
24
|
-
BaseProvider,
|
|
25
25
|
HTTPProvider,
|
|
26
26
|
IPCProvider,
|
|
27
|
+
JSONBaseProvider,
|
|
27
28
|
LegacyWebSocketProvider,
|
|
28
29
|
)
|
|
29
30
|
from web3.types import (
|
|
@@ -35,7 +36,7 @@ HTTP_SCHEMES = {"http", "https"}
|
|
|
35
36
|
WS_SCHEMES = {"ws", "wss"}
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def load_provider_from_environment() ->
|
|
39
|
+
def load_provider_from_environment() -> Optional[JSONBaseProvider]:
|
|
39
40
|
uri_string = URI(os.environ.get("WEB3_PROVIDER_URI", ""))
|
|
40
41
|
if not uri_string:
|
|
41
42
|
return None
|
|
@@ -45,7 +46,7 @@ def load_provider_from_environment() -> BaseProvider:
|
|
|
45
46
|
|
|
46
47
|
def load_provider_from_uri(
|
|
47
48
|
uri_string: URI, headers: Optional[Dict[str, Tuple[str, str]]] = None
|
|
48
|
-
) ->
|
|
49
|
+
) -> JSONBaseProvider:
|
|
49
50
|
uri = urlparse(uri_string)
|
|
50
51
|
if uri.scheme == "file":
|
|
51
52
|
return IPCProvider(uri.path)
|
|
@@ -60,7 +61,7 @@ def load_provider_from_uri(
|
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
class AutoProvider(
|
|
64
|
+
class AutoProvider(JSONBaseProvider):
|
|
64
65
|
default_providers = (
|
|
65
66
|
load_provider_from_environment,
|
|
66
67
|
IPCProvider,
|
|
@@ -72,7 +73,7 @@ class AutoProvider(BaseProvider):
|
|
|
72
73
|
def __init__(
|
|
73
74
|
self,
|
|
74
75
|
potential_providers: Optional[
|
|
75
|
-
Sequence[Union[Callable[...,
|
|
76
|
+
Sequence[Union[Callable[..., JSONBaseProvider], Type[JSONBaseProvider]]]
|
|
76
77
|
] = None,
|
|
77
78
|
) -> None:
|
|
78
79
|
"""
|
|
@@ -83,6 +84,7 @@ class AutoProvider(BaseProvider):
|
|
|
83
84
|
in an attempt to find an active node. The list will default to
|
|
84
85
|
:attribute:`default_providers`.
|
|
85
86
|
"""
|
|
87
|
+
super().__init__()
|
|
86
88
|
if potential_providers:
|
|
87
89
|
self._potential_providers = potential_providers
|
|
88
90
|
else:
|
|
@@ -94,6 +96,14 @@ class AutoProvider(BaseProvider):
|
|
|
94
96
|
except OSError:
|
|
95
97
|
return self._proxy_request(method, params, use_cache=False)
|
|
96
98
|
|
|
99
|
+
def make_batch_request(
|
|
100
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
101
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
102
|
+
try:
|
|
103
|
+
return self._proxy_batch_request(requests)
|
|
104
|
+
except OSError:
|
|
105
|
+
return self._proxy_batch_request(requests, use_cache=False)
|
|
106
|
+
|
|
97
107
|
def is_connected(self, show_traceback: bool = False) -> bool:
|
|
98
108
|
provider = self._get_active_provider(use_cache=True)
|
|
99
109
|
return provider is not None and provider.is_connected(show_traceback)
|
|
@@ -110,7 +120,19 @@ class AutoProvider(BaseProvider):
|
|
|
110
120
|
|
|
111
121
|
return provider.make_request(method, params)
|
|
112
122
|
|
|
113
|
-
def
|
|
123
|
+
def _proxy_batch_request(
|
|
124
|
+
self, requests: List[Tuple[RPCEndpoint, Any]], use_cache: bool = True
|
|
125
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
126
|
+
provider = self._get_active_provider(use_cache)
|
|
127
|
+
if provider is None:
|
|
128
|
+
raise CannotHandleRequest(
|
|
129
|
+
"Could not discover provider while making batch request: "
|
|
130
|
+
f"requests:{requests}\n"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return provider.make_batch_request(requests)
|
|
134
|
+
|
|
135
|
+
def _get_active_provider(self, use_cache: bool) -> Optional[JSONBaseProvider]:
|
|
114
136
|
if use_cache and self._active_provider is not None:
|
|
115
137
|
return self._active_provider
|
|
116
138
|
|
web3/providers/ipc.py
CHANGED
|
@@ -59,7 +59,7 @@ def get_ipc_socket(ipc_path: str, timeout: float = 2.0) -> socket.socket:
|
|
|
59
59
|
return sock
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class
|
|
62
|
+
class PersistentSocket:
|
|
63
63
|
sock = None
|
|
64
64
|
|
|
65
65
|
def __init__(self, ipc_path: str) -> None:
|
|
@@ -157,7 +157,7 @@ class IPCProvider(JSONBaseProvider):
|
|
|
157
157
|
|
|
158
158
|
self.timeout = timeout
|
|
159
159
|
self._lock = threading.Lock()
|
|
160
|
-
self._socket =
|
|
160
|
+
self._socket = PersistentSocket(self.ipc_path)
|
|
161
161
|
|
|
162
162
|
def __str__(self) -> str:
|
|
163
163
|
return f"<{self.__class__.__name__} {self.ipc_path}>"
|
|
@@ -164,7 +164,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
164
164
|
if cache_key != self._send_batch_func_cache[0]:
|
|
165
165
|
|
|
166
166
|
async def send_func(
|
|
167
|
-
requests: List[Tuple[RPCEndpoint, Any]]
|
|
167
|
+
requests: List[Tuple[RPCEndpoint, Any]],
|
|
168
168
|
) -> List[RPCRequest]:
|
|
169
169
|
for mw in middleware:
|
|
170
170
|
initialized = mw(async_w3)
|
|
@@ -376,11 +376,12 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
376
376
|
) -> None:
|
|
377
377
|
# Puts a `TaskNotRunning` in appropriate queues to signal the end of the
|
|
378
378
|
# listener task to any listeners relying on the queues.
|
|
379
|
+
message = "Message listener task has ended."
|
|
379
380
|
self._request_processor._subscription_response_queue.put_nowait(
|
|
380
|
-
TaskNotRunning(message_listener_task)
|
|
381
|
+
TaskNotRunning(message_listener_task, message=message)
|
|
381
382
|
)
|
|
382
383
|
self._request_processor._handler_subscription_queue.put_nowait(
|
|
383
|
-
TaskNotRunning(message_listener_task)
|
|
384
|
+
TaskNotRunning(message_listener_task, message=message)
|
|
384
385
|
)
|
|
385
386
|
|
|
386
387
|
def _raise_stray_errors_from_cache(self) -> None:
|
|
@@ -5,6 +5,7 @@ from typing import (
|
|
|
5
5
|
Any,
|
|
6
6
|
List,
|
|
7
7
|
Sequence,
|
|
8
|
+
Set,
|
|
8
9
|
Union,
|
|
9
10
|
cast,
|
|
10
11
|
overload,
|
|
@@ -15,6 +16,7 @@ from eth_typing import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from web3.exceptions import (
|
|
19
|
+
SubscriptionHandlerTaskException,
|
|
18
20
|
SubscriptionProcessingFinished,
|
|
19
21
|
TaskNotRunning,
|
|
20
22
|
Web3TypeError,
|
|
@@ -50,19 +52,26 @@ class SubscriptionManager:
|
|
|
50
52
|
logger: logging.Logger = logging.getLogger(
|
|
51
53
|
"web3.providers.persistent.subscription_manager"
|
|
52
54
|
)
|
|
53
|
-
total_handler_calls: int = 0
|
|
54
55
|
|
|
55
56
|
def __init__(self, w3: "AsyncWeb3") -> None:
|
|
56
57
|
self._w3 = w3
|
|
57
58
|
self._provider = cast("PersistentConnectionProvider", w3.provider)
|
|
58
59
|
self._subscription_container = SubscriptionContainer()
|
|
59
60
|
|
|
61
|
+
# parallelize all subscription handler calls
|
|
62
|
+
self.parallelize = False
|
|
63
|
+
self.task_timeout = 1
|
|
64
|
+
# TODO: can remove quotes from type hints once Python 3.8 support is dropped
|
|
65
|
+
self._tasks: Set["asyncio.Task[None]"] = set()
|
|
66
|
+
|
|
60
67
|
# share the subscription container with the request processor so it can separate
|
|
61
68
|
# subscriptions into different queues based on ``sub._handler`` presence
|
|
62
69
|
self._provider._request_processor._subscription_container = (
|
|
63
70
|
self._subscription_container
|
|
64
71
|
)
|
|
65
72
|
|
|
73
|
+
self.total_handler_calls: int = 0
|
|
74
|
+
|
|
66
75
|
def _add_subscription(self, subscription: EthSubscription[Any]) -> None:
|
|
67
76
|
self._subscription_container.add_subscription(subscription)
|
|
68
77
|
|
|
@@ -86,6 +95,35 @@ class SubscriptionManager:
|
|
|
86
95
|
f"labels.\n label: {subscription._label}"
|
|
87
96
|
)
|
|
88
97
|
|
|
98
|
+
# TODO: can remove quotes from type hints once Python 3.8 support is dropped
|
|
99
|
+
def _handler_task_callback(self, task: "asyncio.Task[None]") -> None:
|
|
100
|
+
"""
|
|
101
|
+
Callback when a handler task completes. Similar to _message_listener_callback.
|
|
102
|
+
Puts handler exceptions into the queue to be raised in the main loop, else
|
|
103
|
+
removes the task from the set of active tasks.
|
|
104
|
+
"""
|
|
105
|
+
if task.done() and not task.cancelled():
|
|
106
|
+
try:
|
|
107
|
+
task.result()
|
|
108
|
+
self._tasks.discard(task)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.logger.exception("Subscription handler task raised an exception.")
|
|
111
|
+
self._provider._request_processor._handler_subscription_queue.put_nowait( # noqa: E501
|
|
112
|
+
SubscriptionHandlerTaskException(task, message=str(e))
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def _cleanup_remaining_tasks(self) -> None:
|
|
116
|
+
"""Cancel and clean up all remaining tasks."""
|
|
117
|
+
if not self._tasks:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
self.logger.debug("Cleaning up %d remaining tasks...", len(self._tasks))
|
|
121
|
+
for task in self._tasks:
|
|
122
|
+
if not task.done():
|
|
123
|
+
task.cancel()
|
|
124
|
+
|
|
125
|
+
self._tasks.clear()
|
|
126
|
+
|
|
89
127
|
@property
|
|
90
128
|
def subscriptions(self) -> List[EthSubscription[Any]]:
|
|
91
129
|
return self._subscription_container.subscriptions
|
|
@@ -281,14 +319,23 @@ class SubscriptionManager:
|
|
|
281
319
|
sub_id
|
|
282
320
|
)
|
|
283
321
|
if sub:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
**sub._handler_context,
|
|
290
|
-
)
|
|
322
|
+
sub_context = EthSubscriptionContext(
|
|
323
|
+
self._w3,
|
|
324
|
+
sub,
|
|
325
|
+
formatted_sub_response["result"],
|
|
326
|
+
**sub._handler_context,
|
|
291
327
|
)
|
|
328
|
+
if sub.parallelize is True or (
|
|
329
|
+
sub.parallelize is None and self.parallelize
|
|
330
|
+
):
|
|
331
|
+
# run the handler in a task to allow parallel processing
|
|
332
|
+
task = asyncio.create_task(sub._handler(sub_context))
|
|
333
|
+
self._tasks.add(task)
|
|
334
|
+
task.add_done_callback(self._handler_task_callback)
|
|
335
|
+
else:
|
|
336
|
+
# await the handler in the main loop to ensure order
|
|
337
|
+
await sub._handler(sub_context)
|
|
338
|
+
|
|
292
339
|
except SubscriptionProcessingFinished:
|
|
293
340
|
if not run_forever:
|
|
294
341
|
self.logger.info(
|
|
@@ -296,14 +343,20 @@ class SubscriptionManager:
|
|
|
296
343
|
"Stopping subscription handling."
|
|
297
344
|
)
|
|
298
345
|
break
|
|
299
|
-
except
|
|
300
|
-
await asyncio.sleep(0)
|
|
301
|
-
self._provider._handle_listener_task_exceptions()
|
|
346
|
+
except SubscriptionHandlerTaskException:
|
|
302
347
|
self.logger.error(
|
|
303
|
-
"
|
|
304
|
-
"
|
|
348
|
+
"An exception occurred in a subscription handler task. "
|
|
349
|
+
"Stopping subscription handling."
|
|
305
350
|
)
|
|
351
|
+
await self._cleanup_remaining_tasks()
|
|
352
|
+
raise
|
|
353
|
+
except TaskNotRunning as e:
|
|
354
|
+
self.logger.error("Stopping subscription handling: %s", e.message)
|
|
355
|
+
self._provider._handle_listener_task_exceptions()
|
|
306
356
|
break
|
|
307
357
|
|
|
308
358
|
# no active handler subscriptions, clear the handler subscription queue
|
|
309
359
|
self._provider._request_processor._reset_handler_subscription_queue()
|
|
360
|
+
|
|
361
|
+
if self._tasks:
|
|
362
|
+
await self._cleanup_remaining_tasks()
|
web3/utils/subscriptions.py
CHANGED
|
@@ -110,11 +110,14 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
110
110
|
handler: Optional[EthSubscriptionHandler] = None,
|
|
111
111
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
112
112
|
label: Optional[str] = None,
|
|
113
|
+
parallelize: Optional[bool] = None,
|
|
113
114
|
) -> None:
|
|
114
115
|
self._subscription_params = subscription_params
|
|
115
116
|
self._handler = handler_wrapper(handler)
|
|
116
117
|
self._handler_context = handler_context or {}
|
|
117
118
|
self._label = label
|
|
119
|
+
|
|
120
|
+
self.parallelize = parallelize
|
|
118
121
|
self.handler_call_count = 0
|
|
119
122
|
|
|
120
123
|
@property
|
|
@@ -128,6 +131,7 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
128
131
|
handler: Optional[EthSubscriptionHandler] = None,
|
|
129
132
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
130
133
|
label: Optional[str] = None,
|
|
134
|
+
parallelize: Optional[bool] = None,
|
|
131
135
|
) -> "EthSubscription[Any]":
|
|
132
136
|
subscription_type = subscription_params[0]
|
|
133
137
|
subscription_arg = (
|
|
@@ -135,7 +139,10 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
135
139
|
)
|
|
136
140
|
if subscription_type == "newHeads":
|
|
137
141
|
return NewHeadsSubscription(
|
|
138
|
-
handler=handler,
|
|
142
|
+
handler=handler,
|
|
143
|
+
handler_context=handler_context,
|
|
144
|
+
label=label,
|
|
145
|
+
parallelize=parallelize,
|
|
139
146
|
)
|
|
140
147
|
elif subscription_type == "logs":
|
|
141
148
|
subscription_arg = subscription_arg or {}
|
|
@@ -144,6 +151,7 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
144
151
|
handler=handler,
|
|
145
152
|
handler_context=handler_context,
|
|
146
153
|
label=label,
|
|
154
|
+
parallelize=parallelize,
|
|
147
155
|
)
|
|
148
156
|
elif subscription_type == "newPendingTransactions":
|
|
149
157
|
subscription_arg = subscription_arg or False
|
|
@@ -152,10 +160,14 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
152
160
|
handler=handler,
|
|
153
161
|
handler_context=handler_context,
|
|
154
162
|
label=label,
|
|
163
|
+
parallelize=parallelize,
|
|
155
164
|
)
|
|
156
165
|
elif subscription_type == "syncing":
|
|
157
166
|
return SyncingSubscription(
|
|
158
|
-
handler=handler,
|
|
167
|
+
handler=handler,
|
|
168
|
+
handler_context=handler_context,
|
|
169
|
+
label=label,
|
|
170
|
+
parallelize=parallelize,
|
|
159
171
|
)
|
|
160
172
|
else:
|
|
161
173
|
params = (
|
|
@@ -168,6 +180,7 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
168
180
|
handler=handler,
|
|
169
181
|
handler_context=handler_context,
|
|
170
182
|
label=label,
|
|
183
|
+
parallelize=parallelize,
|
|
171
184
|
)
|
|
172
185
|
|
|
173
186
|
@property
|
|
@@ -206,6 +219,7 @@ class LogsSubscription(EthSubscription[LogReceipt]):
|
|
|
206
219
|
handler: LogsSubscriptionHandler = None,
|
|
207
220
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
208
221
|
label: Optional[str] = None,
|
|
222
|
+
parallelize: Optional[bool] = None,
|
|
209
223
|
) -> None:
|
|
210
224
|
self.address = address
|
|
211
225
|
self.topics = topics
|
|
@@ -222,6 +236,7 @@ class LogsSubscription(EthSubscription[LogReceipt]):
|
|
|
222
236
|
handler=handler,
|
|
223
237
|
handler_context=handler_context,
|
|
224
238
|
label=label,
|
|
239
|
+
parallelize=parallelize,
|
|
225
240
|
)
|
|
226
241
|
|
|
227
242
|
|
|
@@ -237,12 +252,14 @@ class NewHeadsSubscription(EthSubscription[BlockData]):
|
|
|
237
252
|
label: Optional[str] = None,
|
|
238
253
|
handler: Optional[NewHeadsSubscriptionHandler] = None,
|
|
239
254
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
255
|
+
parallelize: Optional[bool] = None,
|
|
240
256
|
) -> None:
|
|
241
257
|
super().__init__(
|
|
242
258
|
subscription_params=("newHeads",),
|
|
243
259
|
handler=handler,
|
|
244
260
|
handler_context=handler_context,
|
|
245
261
|
label=label,
|
|
262
|
+
parallelize=parallelize,
|
|
246
263
|
)
|
|
247
264
|
|
|
248
265
|
|
|
@@ -261,6 +278,7 @@ class PendingTxSubscription(EthSubscription[Union[HexBytes, TxData]]):
|
|
|
261
278
|
label: Optional[str] = None,
|
|
262
279
|
handler: Optional[PendingTxSubscriptionHandler] = None,
|
|
263
280
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
281
|
+
parallelize: Optional[bool] = None,
|
|
264
282
|
) -> None:
|
|
265
283
|
self.full_transactions = full_transactions
|
|
266
284
|
super().__init__(
|
|
@@ -268,6 +286,7 @@ class PendingTxSubscription(EthSubscription[Union[HexBytes, TxData]]):
|
|
|
268
286
|
handler=handler,
|
|
269
287
|
handler_context=handler_context,
|
|
270
288
|
label=label,
|
|
289
|
+
parallelize=parallelize,
|
|
271
290
|
)
|
|
272
291
|
|
|
273
292
|
|
|
@@ -283,10 +302,12 @@ class SyncingSubscription(EthSubscription[SyncProgress]):
|
|
|
283
302
|
label: Optional[str] = None,
|
|
284
303
|
handler: Optional[SyncingSubscriptionHandler] = None,
|
|
285
304
|
handler_context: Optional[Dict[str, Any]] = None,
|
|
305
|
+
parallelize: Optional[bool] = None,
|
|
286
306
|
) -> None:
|
|
287
307
|
super().__init__(
|
|
288
308
|
subscription_params=("syncing",),
|
|
289
309
|
handler=handler,
|
|
290
310
|
handler_context=handler_context,
|
|
291
311
|
label=label,
|
|
312
|
+
parallelize=parallelize,
|
|
292
313
|
)
|
|
@@ -14,11 +14,11 @@ ens/specs/normalization_spec.json,sha256=8mmjBj4OoYCn7pD4P7hqKP_qy6rpYzpyRinSH3C
|
|
|
14
14
|
web3/__init__.py,sha256=P11QAEV_GYoZq9ij8gDzFx5tKzJY2aMXG-keg2Lg1xs,1277
|
|
15
15
|
web3/constants.py,sha256=eQLRQVMFPbgpOjjkPTMHkY-syncJuO-sPX5UrCSRjzQ,564
|
|
16
16
|
web3/datastructures.py,sha256=J5rdpnuS10pKUfPYqcoorkDw-aHHZNrdiW-Bh7dVDwI,11514
|
|
17
|
-
web3/exceptions.py,sha256=
|
|
17
|
+
web3/exceptions.py,sha256=9qWjvZM-snQTITs8FJbTMnVQygM80ciddwOkSgiioY8,9783
|
|
18
18
|
web3/geth.py,sha256=xVBZWSksBo2ipesAN9V5hzDc_te7kU8ueicFdvpkSO4,7370
|
|
19
19
|
web3/logs.py,sha256=ROs-mDMH_ZOecE7hfbWA5yp27G38FbLjX4lO_WtlZxQ,198
|
|
20
20
|
web3/main.py,sha256=eVdSh7m_iBMf3au0Aj49TZ7NSaRbN1Ccsng9Fuu8dME,16162
|
|
21
|
-
web3/manager.py,sha256=
|
|
21
|
+
web3/manager.py,sha256=jlHJoGdhFFSsZNTbdUXVakchJozxqrN_NPWp1gUSMgs,22588
|
|
22
22
|
web3/method.py,sha256=BCYend146F5Q149VB2VN11_Z3x8y0lJZH8ShF_VxwV4,8682
|
|
23
23
|
web3/module.py,sha256=CDlnDrrWzkCJtd3gzHZ972l-6En6IyFEWwB7TXkHHLM,5617
|
|
24
24
|
web3/net.py,sha256=Y3vPzHWVFkfHEZoJxjDOt4tp5ERmZrMuyi4ZFOLmIeA,1562
|
|
@@ -89,7 +89,7 @@ web3/_utils/contract_sources/contract_data/storage_contract.py,sha256=ARz6J3Gmsn
|
|
|
89
89
|
web3/_utils/contract_sources/contract_data/string_contract.py,sha256=sk6TWvzH7GkRxwioXFm2a7J99riySqipn2EQhfYdzLY,11228
|
|
90
90
|
web3/_utils/contract_sources/contract_data/tuple_contracts.py,sha256=7RlKfKRVXhjJN1j7D4YlQM6BswK3c0VaSxcnPVwAgcg,23176
|
|
91
91
|
web3/_utils/module_testing/__init__.py,sha256=Xr_S46cjr0mypD_Y4ZbeF1EJ-XWfNxWUks5ykhzN10c,485
|
|
92
|
-
web3/_utils/module_testing/eth_module.py,sha256=
|
|
92
|
+
web3/_utils/module_testing/eth_module.py,sha256=g4NW9m6jU5IXjWxp0Cl3UoVB3mC1FrHLt3ljztpZx4I,196504
|
|
93
93
|
web3/_utils/module_testing/go_ethereum_admin_module.py,sha256=_c-6SyzZkfAJ-7ySXUpw9FEr4cg-ShRdAGSAHWanCtY,3406
|
|
94
94
|
web3/_utils/module_testing/go_ethereum_debug_module.py,sha256=BP1UjK-5ewkYMilvW9jtZX5Mc9BGh3QlJWPXqDNWizU,4144
|
|
95
95
|
web3/_utils/module_testing/go_ethereum_txpool_module.py,sha256=5f8XL8-2x3keyGRaITxMQYl9oQzjgqGn8zobB-j9BPs,1176
|
|
@@ -110,7 +110,7 @@ web3/contract/base_contract.py,sha256=vQVcne9luQ35eBheRh1Jc_jXOp2uDxIotKK9O1EQjQ
|
|
|
110
110
|
web3/contract/contract.py,sha256=-TJJMjwybZmQo8-DtGhrjIr7TwVXFoiIsdMan1NFgQY,20562
|
|
111
111
|
web3/contract/utils.py,sha256=QwJFbgojLKRb_7DrwDmXI2acwRk7zMNXHGvH5JaR1NI,18591
|
|
112
112
|
web3/eth/__init__.py,sha256=qDLxOcHHIzzPD7xzwy6Wcs0lLPQieB7WN0Ax25ctit8,197
|
|
113
|
-
web3/eth/async_eth.py,sha256=
|
|
113
|
+
web3/eth/async_eth.py,sha256=Uz-0W2rAk8tygbk5xJkffnAA8v-mneYrYsFePViGcTI,24535
|
|
114
114
|
web3/eth/base_eth.py,sha256=UUH0Fw0HVa_mBEQ_CbCDO01yCVDsj33d8yOv7Oe-QD0,6905
|
|
115
115
|
web3/eth/eth.py,sha256=zYhotz6GFRVl951oljXHnnlF1GJTgFlgJlA09Soji5k,20806
|
|
116
116
|
web3/gas_strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -121,7 +121,7 @@ web3/middleware/attrdict.py,sha256=tIoMEZ3BkmEafnwitGY70o0GS9ShfwReDMxkHuvcOwI,2
|
|
|
121
121
|
web3/middleware/base.py,sha256=jUY19tw6iiJenDprYqkTeIESd8qPcTvNALP3Vhp86qk,5728
|
|
122
122
|
web3/middleware/buffered_gas_estimate.py,sha256=EmxUd-uO959UVroPsPKkl7oDa8Tw6N8BQLB6Urng5Eo,1647
|
|
123
123
|
web3/middleware/filter.py,sha256=I09sSE_q_dhWX5_24KVWhVXZNevwViI7wucJBP4TZl4,22221
|
|
124
|
-
web3/middleware/formatting.py,sha256=
|
|
124
|
+
web3/middleware/formatting.py,sha256=GPbJes2-ECQW5fbHllajGFDd4b58X-sKLef_DSnYA40,7793
|
|
125
125
|
web3/middleware/gas_price_strategy.py,sha256=ZjZ6pe3z0mDGLZHYoFXp4_fZIePqukljEh9f4mZUyIA,3779
|
|
126
126
|
web3/middleware/names.py,sha256=OBpsvCmcTItth4TcvUNUvcYmINnudtCHq3n6YO_BkNs,4309
|
|
127
127
|
web3/middleware/proof_of_authority.py,sha256=0AT4jr5CmTdrvl8Jiy-WYy8IFDYBOEaesgHDwpn0c7M,1429
|
|
@@ -131,9 +131,9 @@ web3/middleware/stalecheck.py,sha256=oWRA69BGIbNGjHSnUVOBnoxOYJZYjzRzlqqL5RRlnzk
|
|
|
131
131
|
web3/middleware/validation.py,sha256=QxActrJk_zsXXiwpadP2MUjZBS5E50OJOtUwVrm9XVo,4280
|
|
132
132
|
web3/providers/__init__.py,sha256=YkcSzE9AubvSp-UfvJjyCrdepvziysbqeq2LT0ImDoc,936
|
|
133
133
|
web3/providers/async_base.py,sha256=vCx4SAHPSACrRps4KZ36xgjAMQxKXqXsYB8jACodwWo,8241
|
|
134
|
-
web3/providers/auto.py,sha256=
|
|
134
|
+
web3/providers/auto.py,sha256=9Jacts6375zHEG969lm5LOdmnhG4kJFnh4jMYOySQWU,4300
|
|
135
135
|
web3/providers/base.py,sha256=mTLfK5gZzykzfjVkb6QHROFAAeE6ZdS6J6IPfqZr_e4,6876
|
|
136
|
-
web3/providers/ipc.py,sha256=
|
|
136
|
+
web3/providers/ipc.py,sha256=Rc0x4cW3NietQz0BjOygs78GzVGqgz0fHFZM6HalpVg,6518
|
|
137
137
|
web3/providers/legacy_websocket.py,sha256=uQb5SmoFPFI809q_2iRhDEo5SkSW3T9tYXuf48stp9A,4744
|
|
138
138
|
web3/providers/eth_tester/__init__.py,sha256=UggyBQdeAyjy1awATW1933jkJcpqqaUYUQEFAQnA2o0,163
|
|
139
139
|
web3/providers/eth_tester/defaults.py,sha256=QQUdqqrkcN1AKW7WEY1A5RiPc_fmlHCLmdgB-5iY7Dc,12622
|
|
@@ -141,11 +141,11 @@ web3/providers/eth_tester/main.py,sha256=U19sNDeHs36A4IYQ0HFGyXdZvuXiYvoSMNWVuki
|
|
|
141
141
|
web3/providers/eth_tester/middleware.py,sha256=JS-cjGF5BtF43dp-bP7QDv0RWyq1iqwiq81RhTAswjI,13730
|
|
142
142
|
web3/providers/persistent/__init__.py,sha256=X7tFKJL5BXSwciq5_bRwGRB6bfdWBkIPPWMqCjXIKrA,411
|
|
143
143
|
web3/providers/persistent/async_ipc.py,sha256=6HDKo9hIXhag3nyTbp6J-ZktPLnG-9iHCduQUGD7raM,5049
|
|
144
|
-
web3/providers/persistent/persistent.py,sha256=
|
|
144
|
+
web3/providers/persistent/persistent.py,sha256=tM0Opg5i_oNlW_vmkqnzkAAPww36E5_jhonXDWfhc4Y,19444
|
|
145
145
|
web3/providers/persistent/persistent_connection.py,sha256=NxxS-KeJhV07agg8CtJvmE-Ff-wLggQYpz4gdgVRDNU,3011
|
|
146
146
|
web3/providers/persistent/request_processor.py,sha256=QLVn5MiNwDVsO9hsHPlkUH4zW4H74HRK-duPot4H4Yk,14141
|
|
147
147
|
web3/providers/persistent/subscription_container.py,sha256=yd5pjjz_YnRLuUoxZUxt29Md1VUTemdUIBq8PCJre6Y,1734
|
|
148
|
-
web3/providers/persistent/subscription_manager.py,sha256=
|
|
148
|
+
web3/providers/persistent/subscription_manager.py,sha256=QByJn3qfOT1Lt7L6BMUqNSeu_FajAGZRwLvawSajsic,13956
|
|
149
149
|
web3/providers/persistent/utils.py,sha256=gfY7w1HB8xuE7OujSrbwWYjQuQ8nzRBoxoL8ESinqWM,1140
|
|
150
150
|
web3/providers/persistent/websocket.py,sha256=STf31VNdwidMeAeeL1r5f8v3l66xChKkxZpnZzUiYO8,4577
|
|
151
151
|
web3/providers/rpc/__init__.py,sha256=mObsuwjr7xyHnnRlwzsmbp2JgZdn2NXSSctvpye4AuQ,149
|
|
@@ -163,9 +163,9 @@ web3/utils/address.py,sha256=nzPLiWWCG9BqstDeDOcDwEpteJ8im6ywjLHKpd5akhw,1186
|
|
|
163
163
|
web3/utils/async_exception_handling.py,sha256=OoKbLNwWcY9dxLCbOfxcQPSB1OxWraNqcw8V0ZX-JaQ,3173
|
|
164
164
|
web3/utils/caching.py,sha256=miulUjLOjlOfTux8HWBklpRIa6_fVNTVFHIWcbZt27o,2591
|
|
165
165
|
web3/utils/exception_handling.py,sha256=n-MtO5LNzJDVzHTzO6olzfb2_qEVtVRvink0ixswg-Y,2917
|
|
166
|
-
web3/utils/subscriptions.py,sha256=
|
|
167
|
-
web3-7.
|
|
168
|
-
web3-7.
|
|
169
|
-
web3-7.
|
|
170
|
-
web3-7.
|
|
171
|
-
web3-7.
|
|
166
|
+
web3/utils/subscriptions.py,sha256=XF0bN_OjIkweSTQmB0U0nMJrLCeNYYuukoUFiV5skV0,9340
|
|
167
|
+
web3-7.13.0.dist-info/licenses/LICENSE,sha256=ENGC4gSn0kYaC_mlaXOEwCKmA6W7Z9MeSemc5O2k-h0,1095
|
|
168
|
+
web3-7.13.0.dist-info/METADATA,sha256=8-k-l2hjPirh12SotNzYaXchRfwcPo2GJCSLB5gVs3s,5621
|
|
169
|
+
web3-7.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
170
|
+
web3-7.13.0.dist-info/top_level.txt,sha256=iwupuJh7wgypXrpk_awszyri3TahRr5vxSphNyvt1bU,9
|
|
171
|
+
web3-7.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|