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/middleware/base.py
CHANGED
|
@@ -62,15 +62,19 @@ class Web3Middleware:
|
|
|
62
62
|
) -> "MakeBatchRequestFn":
|
|
63
63
|
def middleware(
|
|
64
64
|
requests_info: List[Tuple["RPCEndpoint", Any]]
|
|
65
|
-
) -> List["RPCResponse"]:
|
|
65
|
+
) -> Union[List["RPCResponse"], "RPCResponse"]:
|
|
66
66
|
req_processed = [
|
|
67
67
|
self.request_processor(method, params)
|
|
68
68
|
for (method, params) in requests_info
|
|
69
69
|
]
|
|
70
|
-
|
|
70
|
+
response = make_batch_request(req_processed)
|
|
71
|
+
if not isinstance(response, list):
|
|
72
|
+
# RPC errors return only one response with the error object
|
|
73
|
+
return response
|
|
74
|
+
|
|
71
75
|
methods, _params = zip(*req_processed)
|
|
72
76
|
formatted_responses = [
|
|
73
|
-
self.response_processor(m, r) for m, r in zip(methods,
|
|
77
|
+
self.response_processor(m, r) for m, r in zip(methods, response)
|
|
74
78
|
]
|
|
75
79
|
return formatted_responses
|
|
76
80
|
|
|
@@ -103,16 +107,20 @@ class Web3Middleware:
|
|
|
103
107
|
) -> "AsyncMakeBatchRequestFn":
|
|
104
108
|
async def middleware(
|
|
105
109
|
requests_info: List[Tuple["RPCEndpoint", Any]]
|
|
106
|
-
) -> List["RPCResponse"]:
|
|
110
|
+
) -> Union[List["RPCResponse"], "RPCResponse"]:
|
|
107
111
|
req_processed = [
|
|
108
112
|
await self.async_request_processor(method, params)
|
|
109
113
|
for (method, params) in requests_info
|
|
110
114
|
]
|
|
111
|
-
|
|
115
|
+
response = await make_batch_request(req_processed)
|
|
116
|
+
if not isinstance(response, list):
|
|
117
|
+
# RPC errors return only one response with the error object
|
|
118
|
+
return response
|
|
119
|
+
|
|
112
120
|
methods, _params = zip(*req_processed)
|
|
113
121
|
formatted_responses = [
|
|
114
122
|
await self.async_response_processor(m, r)
|
|
115
|
-
for m, r in zip(methods,
|
|
123
|
+
for m, r in zip(methods, response)
|
|
116
124
|
]
|
|
117
125
|
return formatted_responses
|
|
118
126
|
|
web3/providers/async_base.py
CHANGED
|
@@ -77,7 +77,8 @@ class AsyncBaseProvider:
|
|
|
77
77
|
|
|
78
78
|
_is_batching: bool = False
|
|
79
79
|
_batch_request_func_cache: Tuple[
|
|
80
|
-
Tuple[Middleware, ...],
|
|
80
|
+
Tuple[Middleware, ...],
|
|
81
|
+
Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]],
|
|
81
82
|
] = (None, None)
|
|
82
83
|
|
|
83
84
|
is_async = True
|
|
@@ -119,7 +120,7 @@ class AsyncBaseProvider:
|
|
|
119
120
|
|
|
120
121
|
async def batch_request_func(
|
|
121
122
|
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
122
|
-
) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
|
|
123
|
+
) -> Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]]:
|
|
123
124
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
124
125
|
|
|
125
126
|
cache_key = self._batch_request_func_cache[0]
|
|
@@ -141,8 +142,8 @@ class AsyncBaseProvider:
|
|
|
141
142
|
|
|
142
143
|
async def make_batch_request(
|
|
143
144
|
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
144
|
-
) -> List[RPCResponse]:
|
|
145
|
-
raise NotImplementedError("
|
|
145
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
146
|
+
raise NotImplementedError("Providers must implement this method")
|
|
146
147
|
|
|
147
148
|
async def is_connected(self, show_traceback: bool = False) -> bool:
|
|
148
149
|
raise NotImplementedError("Providers must implement this method")
|
web3/providers/base.py
CHANGED
|
@@ -118,7 +118,7 @@ class JSONBaseProvider(BaseProvider):
|
|
|
118
118
|
|
|
119
119
|
_is_batching: bool = False
|
|
120
120
|
_batch_request_func_cache: Tuple[
|
|
121
|
-
Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
|
|
121
|
+
Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
|
|
122
122
|
] = (None, None)
|
|
123
123
|
|
|
124
124
|
def __init__(self, **kwargs: Any) -> None:
|
|
@@ -168,7 +168,7 @@ class JSONBaseProvider(BaseProvider):
|
|
|
168
168
|
|
|
169
169
|
def batch_request_func(
|
|
170
170
|
self, w3: "Web3", middleware_onion: MiddlewareOnion
|
|
171
|
-
) -> Callable[..., List[RPCResponse]]:
|
|
171
|
+
) -> Callable[..., Union[List[RPCResponse], RPCResponse]]:
|
|
172
172
|
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
173
173
|
|
|
174
174
|
cache_key = self._batch_request_func_cache[0]
|
|
@@ -199,5 +199,5 @@ class JSONBaseProvider(BaseProvider):
|
|
|
199
199
|
|
|
200
200
|
def make_batch_request(
|
|
201
201
|
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
202
|
-
) -> List[RPCResponse]:
|
|
202
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
203
203
|
raise NotImplementedError("Providers must implement this method")
|
|
@@ -33,6 +33,9 @@ from web3._utils.caching.caching_utils import (
|
|
|
33
33
|
async_handle_recv_caching,
|
|
34
34
|
async_handle_send_caching,
|
|
35
35
|
)
|
|
36
|
+
from web3._utils.validation import (
|
|
37
|
+
validate_rpc_response_and_raise_if_error,
|
|
38
|
+
)
|
|
36
39
|
from web3.exceptions import (
|
|
37
40
|
PersistentConnectionClosedOK,
|
|
38
41
|
ProviderConnectionError,
|
|
@@ -302,6 +305,27 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
302
305
|
TaskNotRunning(message_listener_task)
|
|
303
306
|
)
|
|
304
307
|
|
|
308
|
+
def _raise_stray_errors_from_cache(self) -> None:
|
|
309
|
+
"""
|
|
310
|
+
Check the request response cache for any errors not tied to current requests
|
|
311
|
+
and raise them if found.
|
|
312
|
+
"""
|
|
313
|
+
if not self._is_batching:
|
|
314
|
+
for (
|
|
315
|
+
response
|
|
316
|
+
) in self._request_processor._request_response_cache._data.values():
|
|
317
|
+
request = (
|
|
318
|
+
self._request_processor._request_information_cache.get_cache_entry(
|
|
319
|
+
generate_cache_key(response["id"])
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
if "error" in response and request is None:
|
|
323
|
+
# if we find an error response in the cache without a corresponding
|
|
324
|
+
# request, raise the error
|
|
325
|
+
validate_rpc_response_and_raise_if_error(
|
|
326
|
+
response, None, logger=self.logger
|
|
327
|
+
)
|
|
328
|
+
|
|
305
329
|
async def _message_listener(self) -> None:
|
|
306
330
|
self.logger.info(
|
|
307
331
|
f"{self.__class__.__qualname__} listener background task started. Storing "
|
|
@@ -327,6 +351,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
327
351
|
await self._request_processor.cache_raw_response(
|
|
328
352
|
response, subscription=subscription
|
|
329
353
|
)
|
|
354
|
+
self._raise_stray_errors_from_cache()
|
|
330
355
|
except PersistentConnectionClosedOK as e:
|
|
331
356
|
self.logger.info(
|
|
332
357
|
"Message listener background task has ended gracefully: "
|
|
@@ -376,6 +401,10 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
376
401
|
request_cache_key = generate_cache_key(request_id)
|
|
377
402
|
|
|
378
403
|
while True:
|
|
404
|
+
# check if an exception was recorded in the listener task and raise
|
|
405
|
+
# it in the main loop if so
|
|
406
|
+
self._handle_listener_task_exceptions()
|
|
407
|
+
|
|
379
408
|
if request_cache_key in self._request_processor._request_response_cache:
|
|
380
409
|
self.logger.debug(
|
|
381
410
|
f"Popping response for id {request_id} from cache."
|
|
@@ -385,9 +414,6 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
385
414
|
)
|
|
386
415
|
return popped_response
|
|
387
416
|
else:
|
|
388
|
-
# check if an exception was recorded in the listener task and raise
|
|
389
|
-
# it in the main loop if so
|
|
390
|
-
self._handle_listener_task_exceptions()
|
|
391
417
|
await asyncio.sleep(0)
|
|
392
418
|
|
|
393
419
|
try:
|
|
@@ -6,6 +6,7 @@ from typing import (
|
|
|
6
6
|
Callable,
|
|
7
7
|
Dict,
|
|
8
8
|
Generic,
|
|
9
|
+
List,
|
|
9
10
|
Optional,
|
|
10
11
|
Tuple,
|
|
11
12
|
TypeVar,
|
|
@@ -270,6 +271,15 @@ class RequestProcessor:
|
|
|
270
271
|
|
|
271
272
|
# raw response cache
|
|
272
273
|
|
|
274
|
+
def _is_batch_response(
|
|
275
|
+
self, raw_response: Union[List[RPCResponse], RPCResponse]
|
|
276
|
+
) -> bool:
|
|
277
|
+
return isinstance(raw_response, list) or (
|
|
278
|
+
isinstance(raw_response, dict)
|
|
279
|
+
and raw_response.get("id") is None
|
|
280
|
+
and self._provider._is_batching
|
|
281
|
+
)
|
|
282
|
+
|
|
273
283
|
async def cache_raw_response(
|
|
274
284
|
self, raw_response: Any, subscription: bool = False
|
|
275
285
|
) -> None:
|
|
@@ -296,7 +306,7 @@ class RequestProcessor:
|
|
|
296
306
|
# otherwise, put it in the subscription response queue so a response
|
|
297
307
|
# can be yielded by the message stream
|
|
298
308
|
await self._subscription_response_queue.put(raw_response)
|
|
299
|
-
elif
|
|
309
|
+
elif self._is_batch_response(raw_response):
|
|
300
310
|
# Since only one batch should be in the cache at all times, we use a
|
|
301
311
|
# constant cache key for the batch response.
|
|
302
312
|
cache_key = generate_cache_key(BATCH_REQUEST_ID)
|
|
@@ -69,6 +69,23 @@ class SubscriptionManager:
|
|
|
69
69
|
def _remove_subscription(self, subscription: EthSubscription[Any]) -> None:
|
|
70
70
|
self._subscription_container.remove_subscription(subscription)
|
|
71
71
|
|
|
72
|
+
def _validate_and_normalize_label(self, subscription: EthSubscription[Any]) -> None:
|
|
73
|
+
if subscription.label == subscription._default_label:
|
|
74
|
+
# if no custom label was provided, generate a unique label
|
|
75
|
+
i = 2
|
|
76
|
+
while self.get_by_label(subscription._label) is not None:
|
|
77
|
+
subscription._label = f"{subscription._default_label}#{i}"
|
|
78
|
+
i += 1
|
|
79
|
+
else:
|
|
80
|
+
if (
|
|
81
|
+
subscription._label
|
|
82
|
+
in self._subscription_container.subscriptions_by_label
|
|
83
|
+
):
|
|
84
|
+
raise Web3ValueError(
|
|
85
|
+
"Subscription label already exists. Subscriptions must have unique "
|
|
86
|
+
f"labels.\n label: {subscription._label}"
|
|
87
|
+
)
|
|
88
|
+
|
|
72
89
|
@property
|
|
73
90
|
def subscriptions(self) -> List[EthSubscription[Any]]:
|
|
74
91
|
return self._subscription_container.subscriptions
|
|
@@ -100,16 +117,8 @@ class SubscriptionManager:
|
|
|
100
117
|
:return:
|
|
101
118
|
"""
|
|
102
119
|
if isinstance(subscriptions, EthSubscription):
|
|
103
|
-
if (
|
|
104
|
-
subscriptions.label
|
|
105
|
-
in self._subscription_container.subscriptions_by_label
|
|
106
|
-
):
|
|
107
|
-
raise Web3ValueError(
|
|
108
|
-
"Subscription label already exists. Subscriptions must have "
|
|
109
|
-
f"unique labels.\n label: {subscriptions.label}"
|
|
110
|
-
)
|
|
111
|
-
|
|
112
120
|
subscriptions.manager = self
|
|
121
|
+
self._validate_and_normalize_label(subscriptions)
|
|
113
122
|
sub_id = await self._w3.eth._subscribe(*subscriptions.subscription_params)
|
|
114
123
|
subscriptions._id = sub_id
|
|
115
124
|
self._add_subscription(subscriptions)
|
|
@@ -124,34 +133,91 @@ class SubscriptionManager:
|
|
|
124
133
|
|
|
125
134
|
sub_ids: List[HexStr] = []
|
|
126
135
|
for sub in subscriptions:
|
|
127
|
-
await self.subscribe(sub)
|
|
136
|
+
sub_ids.append(await self.subscribe(sub))
|
|
128
137
|
return sub_ids
|
|
129
138
|
raise Web3TypeError("Expected a Subscription or a sequence of Subscriptions.")
|
|
130
139
|
|
|
131
|
-
|
|
140
|
+
@overload
|
|
141
|
+
async def unsubscribe(self, subscriptions: EthSubscription[Any]) -> bool:
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
@overload
|
|
145
|
+
async def unsubscribe(self, subscriptions: HexStr) -> bool:
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
@overload
|
|
149
|
+
async def unsubscribe(
|
|
150
|
+
self,
|
|
151
|
+
subscriptions: Sequence[Union[EthSubscription[Any], HexStr]],
|
|
152
|
+
) -> bool:
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
async def unsubscribe(
|
|
156
|
+
self,
|
|
157
|
+
subscriptions: Union[
|
|
158
|
+
EthSubscription[Any],
|
|
159
|
+
HexStr,
|
|
160
|
+
Sequence[Union[EthSubscription[Any], HexStr]],
|
|
161
|
+
],
|
|
162
|
+
) -> bool:
|
|
132
163
|
"""
|
|
133
|
-
Used to unsubscribe from
|
|
164
|
+
Used to unsubscribe from one or multiple subscriptions.
|
|
134
165
|
|
|
135
|
-
:param
|
|
136
|
-
:type
|
|
137
|
-
|
|
166
|
+
:param subscriptions: The subscription(s) to unsubscribe from.
|
|
167
|
+
:type subscriptions: Union[EthSubscription, Sequence[EthSubscription], HexStr,
|
|
168
|
+
Sequence[HexStr]]
|
|
169
|
+
:return: ``True`` if unsubscribing to all was successful, ``False`` otherwise
|
|
170
|
+
with a warning.
|
|
138
171
|
:rtype: bool
|
|
139
172
|
"""
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
if isinstance(subscriptions, EthSubscription) or isinstance(subscriptions, str):
|
|
174
|
+
if isinstance(subscriptions, str):
|
|
175
|
+
subscription_id = subscriptions
|
|
176
|
+
subscriptions = self.get_by_id(subscription_id)
|
|
177
|
+
if subscriptions is None:
|
|
178
|
+
raise Web3ValueError(
|
|
179
|
+
"Subscription not found or is not being managed by the "
|
|
180
|
+
f"subscription manager.\n id: {subscription_id}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if subscriptions not in self.subscriptions:
|
|
184
|
+
raise Web3ValueError(
|
|
185
|
+
"Subscription not found or is not being managed by the "
|
|
186
|
+
"subscription manager.\n "
|
|
187
|
+
f"label: {subscriptions.label}\n id: {subscriptions._id}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if await self._w3.eth._unsubscribe(subscriptions.id):
|
|
191
|
+
self._remove_subscription(subscriptions)
|
|
192
|
+
self.logger.info(
|
|
193
|
+
"Successfully unsubscribed from subscription:\n "
|
|
194
|
+
f"label: {subscriptions.label}\n id: {subscriptions.id}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if len(self._subscription_container.handler_subscriptions) == 0:
|
|
198
|
+
queue = (
|
|
199
|
+
self._provider._request_processor._handler_subscription_queue
|
|
200
|
+
)
|
|
201
|
+
await queue.put(SubscriptionProcessingFinished())
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
elif isinstance(subscriptions, Sequence):
|
|
205
|
+
if len(subscriptions) == 0:
|
|
206
|
+
raise Web3ValueError("No subscriptions provided.")
|
|
207
|
+
|
|
208
|
+
unsubscribed: List[bool] = []
|
|
209
|
+
# re-create the subscription list to prevent modifying the original list
|
|
210
|
+
# in case ``subscription_manager.subscriptions`` was passed in directly
|
|
211
|
+
subs = [sub for sub in subscriptions]
|
|
212
|
+
for sub in subs:
|
|
213
|
+
if isinstance(sub, str):
|
|
214
|
+
sub = HexStr(sub)
|
|
215
|
+
unsubscribed.append(await self.unsubscribe(sub))
|
|
216
|
+
return all(unsubscribed)
|
|
217
|
+
|
|
218
|
+
self.logger.warning(
|
|
219
|
+
f"Failed to unsubscribe from subscription\n subscription={subscriptions}"
|
|
220
|
+
)
|
|
155
221
|
return False
|
|
156
222
|
|
|
157
223
|
async def unsubscribe_all(self) -> bool:
|
|
@@ -163,7 +229,9 @@ class SubscriptionManager:
|
|
|
163
229
|
:rtype: bool
|
|
164
230
|
"""
|
|
165
231
|
unsubscribed = [
|
|
166
|
-
await self.unsubscribe(sub)
|
|
232
|
+
await self.unsubscribe(sub)
|
|
233
|
+
# use copy to prevent modifying the list while iterating over it
|
|
234
|
+
for sub in self.subscriptions.copy()
|
|
167
235
|
]
|
|
168
236
|
if all(unsubscribed):
|
|
169
237
|
self.logger.info("Successfully unsubscribed from all subscriptions.")
|
|
@@ -186,15 +254,15 @@ class SubscriptionManager:
|
|
|
186
254
|
:type run_forever: bool
|
|
187
255
|
:return: None
|
|
188
256
|
"""
|
|
189
|
-
if not self._subscription_container.handler_subscriptions:
|
|
257
|
+
if not self._subscription_container.handler_subscriptions and not run_forever:
|
|
190
258
|
self.logger.warning(
|
|
191
259
|
"No handler subscriptions found. Subscription handler did not run."
|
|
192
260
|
)
|
|
193
261
|
return
|
|
194
262
|
|
|
195
263
|
queue = self._provider._request_processor._handler_subscription_queue
|
|
196
|
-
|
|
197
|
-
|
|
264
|
+
while run_forever or self._subscription_container.handler_subscriptions:
|
|
265
|
+
try:
|
|
198
266
|
response = cast(RPCResponse, await queue.get())
|
|
199
267
|
formatted_sub_response = cast(
|
|
200
268
|
FormattedEthSubscriptionResponse,
|
|
@@ -216,18 +284,20 @@ class SubscriptionManager:
|
|
|
216
284
|
**sub._handler_context,
|
|
217
285
|
)
|
|
218
286
|
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
287
|
+
except SubscriptionProcessingFinished:
|
|
288
|
+
if not run_forever:
|
|
289
|
+
self.logger.info(
|
|
290
|
+
"All handler subscriptions have been unsubscribed from. "
|
|
291
|
+
"Stopping subscription handling."
|
|
292
|
+
)
|
|
293
|
+
break
|
|
294
|
+
except TaskNotRunning:
|
|
295
|
+
await asyncio.sleep(0)
|
|
296
|
+
self._provider._handle_listener_task_exceptions()
|
|
297
|
+
self.logger.error(
|
|
298
|
+
"Message listener background task for the provider has stopped "
|
|
299
|
+
"unexpectedly. Stopping subscription handling."
|
|
300
|
+
)
|
|
231
301
|
|
|
232
302
|
# no active handler subscriptions, clear the handler subscription queue
|
|
233
303
|
self._provider._request_processor._reset_handler_subscription_queue()
|
|
@@ -63,6 +63,8 @@ class WebSocketProvider(PersistentConnectionProvider):
|
|
|
63
63
|
self,
|
|
64
64
|
endpoint_uri: Optional[Union[URI, str]] = None,
|
|
65
65
|
websocket_kwargs: Optional[Dict[str, Any]] = None,
|
|
66
|
+
# uses binary frames by default
|
|
67
|
+
use_text_frames: Optional[bool] = False,
|
|
66
68
|
# `PersistentConnectionProvider` kwargs can be passed through
|
|
67
69
|
**kwargs: Any,
|
|
68
70
|
) -> None:
|
|
@@ -71,6 +73,7 @@ class WebSocketProvider(PersistentConnectionProvider):
|
|
|
71
73
|
URI(endpoint_uri) if endpoint_uri is not None else get_default_endpoint()
|
|
72
74
|
)
|
|
73
75
|
super().__init__(**kwargs)
|
|
76
|
+
self.use_text_frames = use_text_frames
|
|
74
77
|
self._ws: Optional[WebSocketClientProtocol] = None
|
|
75
78
|
|
|
76
79
|
if not any(
|
|
@@ -118,9 +121,11 @@ class WebSocketProvider(PersistentConnectionProvider):
|
|
|
118
121
|
"Connection to websocket has not been initiated for the provider."
|
|
119
122
|
)
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
payload: Union[bytes, str] = request_data
|
|
125
|
+
if self.use_text_frames:
|
|
126
|
+
payload = request_data.decode("utf-8")
|
|
127
|
+
|
|
128
|
+
await asyncio.wait_for(self._ws.send(payload), timeout=self.request_timeout)
|
|
124
129
|
|
|
125
130
|
async def socket_recv(self) -> RPCResponse:
|
|
126
131
|
raw_response = await self._ws.recv()
|
web3/providers/rpc/async_rpc.py
CHANGED
|
@@ -168,15 +168,20 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
|
|
|
168
168
|
|
|
169
169
|
async def make_batch_request(
|
|
170
170
|
self, batch_requests: List[Tuple[RPCEndpoint, Any]]
|
|
171
|
-
) -> List[RPCResponse]:
|
|
171
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
172
172
|
self.logger.debug(f"Making batch request HTTP - uri: `{self.endpoint_uri}`")
|
|
173
173
|
request_data = self.encode_batch_rpc_request(batch_requests)
|
|
174
174
|
raw_response = await self._request_session_manager.async_make_post_request(
|
|
175
175
|
self.endpoint_uri, request_data, **self.get_request_kwargs()
|
|
176
176
|
)
|
|
177
177
|
self.logger.debug("Received batch response HTTP.")
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
response = self.decode_rpc_response(raw_response)
|
|
179
|
+
if not isinstance(response, list):
|
|
180
|
+
# RPC errors return only one response with the error object
|
|
181
|
+
return response
|
|
182
|
+
return sort_batch_response_by_response_ids(
|
|
183
|
+
cast(List[RPCResponse], sort_batch_response_by_response_ids(response))
|
|
184
|
+
)
|
|
180
185
|
|
|
181
186
|
async def disconnect(self) -> None:
|
|
182
187
|
cache = self._request_session_manager.session_cache
|
web3/providers/rpc/rpc.py
CHANGED
|
@@ -176,12 +176,17 @@ class HTTPProvider(JSONBaseProvider):
|
|
|
176
176
|
|
|
177
177
|
def make_batch_request(
|
|
178
178
|
self, batch_requests: List[Tuple[RPCEndpoint, Any]]
|
|
179
|
-
) -> List[RPCResponse]:
|
|
179
|
+
) -> Union[List[RPCResponse], RPCResponse]:
|
|
180
180
|
self.logger.debug(f"Making batch request HTTP, uri: `{self.endpoint_uri}`")
|
|
181
181
|
request_data = self.encode_batch_rpc_request(batch_requests)
|
|
182
182
|
raw_response = self._request_session_manager.make_post_request(
|
|
183
183
|
self.endpoint_uri, request_data, **self.get_request_kwargs()
|
|
184
184
|
)
|
|
185
185
|
self.logger.debug("Received batch response HTTP.")
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
response = self.decode_rpc_response(raw_response)
|
|
187
|
+
if not isinstance(response, list):
|
|
188
|
+
# RPC errors return only one response with the error object
|
|
189
|
+
return response
|
|
190
|
+
return sort_batch_response_by_response_ids(
|
|
191
|
+
cast(List[RPCResponse], sort_batch_response_by_response_ids(response))
|
|
192
|
+
)
|
web3/types.py
CHANGED
|
@@ -36,13 +36,9 @@ from web3._utils.compat import (
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
if TYPE_CHECKING:
|
|
39
|
-
from web3.contract.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
from web3.contract.contract import ( # noqa: F401
|
|
44
|
-
ContractEvent,
|
|
45
|
-
ContractFunction,
|
|
39
|
+
from web3.contract.base_contract import (
|
|
40
|
+
BaseContractEvent,
|
|
41
|
+
BaseContractFunction,
|
|
46
42
|
)
|
|
47
43
|
from web3.main import ( # noqa: F401
|
|
48
44
|
AsyncWeb3,
|
|
@@ -201,10 +197,10 @@ class LogReceipt(TypedDict):
|
|
|
201
197
|
blockNumber: BlockNumber
|
|
202
198
|
data: HexBytes
|
|
203
199
|
logIndex: int
|
|
200
|
+
removed: bool
|
|
204
201
|
topics: Sequence[HexBytes]
|
|
205
202
|
transactionHash: HexBytes
|
|
206
203
|
transactionIndex: int
|
|
207
|
-
removed: bool
|
|
208
204
|
|
|
209
205
|
|
|
210
206
|
class SubscriptionResponse(TypedDict):
|
|
@@ -301,10 +297,13 @@ class CreateAccessListResponse(TypedDict):
|
|
|
301
297
|
|
|
302
298
|
|
|
303
299
|
MakeRequestFn = Callable[[RPCEndpoint, Any], RPCResponse]
|
|
304
|
-
MakeBatchRequestFn = Callable[
|
|
300
|
+
MakeBatchRequestFn = Callable[
|
|
301
|
+
[List[Tuple[RPCEndpoint, Any]]], Union[List[RPCResponse], RPCResponse]
|
|
302
|
+
]
|
|
305
303
|
AsyncMakeRequestFn = Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]
|
|
306
304
|
AsyncMakeBatchRequestFn = Callable[
|
|
307
|
-
[List[Tuple[RPCEndpoint, Any]]],
|
|
305
|
+
[List[Tuple[RPCEndpoint, Any]]],
|
|
306
|
+
Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]],
|
|
308
307
|
]
|
|
309
308
|
|
|
310
309
|
|
|
@@ -337,7 +336,7 @@ class StateOverrideParams(TypedDict, total=False):
|
|
|
337
336
|
stateDiff: Optional[Dict[HexStr, HexStr]]
|
|
338
337
|
|
|
339
338
|
|
|
340
|
-
StateOverride = Dict[ChecksumAddress, StateOverrideParams]
|
|
339
|
+
StateOverride = Dict[Union[str, Address, ChecksumAddress], StateOverrideParams]
|
|
341
340
|
|
|
342
341
|
|
|
343
342
|
GasPriceStrategy = Union[
|
|
@@ -568,6 +567,30 @@ class OpcodeTrace(TypedDict, total=False):
|
|
|
568
567
|
structLogs: List[StructLog]
|
|
569
568
|
|
|
570
569
|
|
|
570
|
+
class BlockStateCallV1(TypedDict):
|
|
571
|
+
blockOverrides: NotRequired[BlockData]
|
|
572
|
+
stateOverrides: NotRequired[StateOverride]
|
|
573
|
+
calls: Sequence[TxParams]
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class SimulateV1Payload(TypedDict):
|
|
577
|
+
blockStateCalls: Sequence[BlockStateCallV1]
|
|
578
|
+
validation: NotRequired[bool]
|
|
579
|
+
traceTransfers: NotRequired[bool]
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class SimulateV1CallResult(TypedDict):
|
|
583
|
+
returnData: HexBytes
|
|
584
|
+
logs: Sequence[LogReceipt]
|
|
585
|
+
gasUsed: int
|
|
586
|
+
status: int
|
|
587
|
+
error: NotRequired[RPCError]
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class SimulateV1Result(BlockData):
|
|
591
|
+
calls: Sequence[SimulateV1CallResult]
|
|
592
|
+
|
|
593
|
+
|
|
571
594
|
#
|
|
572
595
|
# web3.geth types
|
|
573
596
|
#
|
|
@@ -581,8 +604,8 @@ class GethWallet(TypedDict):
|
|
|
581
604
|
|
|
582
605
|
# Contract types
|
|
583
606
|
|
|
584
|
-
TContractFn = TypeVar("TContractFn", "
|
|
585
|
-
TContractEvent = TypeVar("TContractEvent", "
|
|
607
|
+
TContractFn = TypeVar("TContractFn", bound="BaseContractFunction")
|
|
608
|
+
TContractEvent = TypeVar("TContractEvent", bound="BaseContractEvent")
|
|
586
609
|
|
|
587
610
|
|
|
588
611
|
# Tracing types
|
web3/utils/subscriptions.py
CHANGED
|
@@ -114,6 +114,10 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
114
114
|
self._label = label
|
|
115
115
|
self.handler_call_count = 0
|
|
116
116
|
|
|
117
|
+
@property
|
|
118
|
+
def _default_label(self) -> str:
|
|
119
|
+
return f"{self.__class__.__name__}{self.subscription_params}"
|
|
120
|
+
|
|
117
121
|
@classmethod
|
|
118
122
|
def _create_type_aware_subscription(
|
|
119
123
|
cls,
|
|
@@ -170,7 +174,7 @@ class EthSubscription(Generic[TSubscriptionResult]):
|
|
|
170
174
|
@property
|
|
171
175
|
def label(self) -> str:
|
|
172
176
|
if not self._label:
|
|
173
|
-
self._label =
|
|
177
|
+
self._label = self._default_label
|
|
174
178
|
return self._label
|
|
175
179
|
|
|
176
180
|
@property
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016-
|
|
3
|
+
Copyright (c) 2016-2025 The Ethereum Foundation
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|