web3 6.18.0__py3-none-any.whl → 6.19.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/module_testing_utils.py +13 -0
- web3/exceptions.py +19 -1
- web3/main.py +5 -8
- web3/manager.py +19 -8
- web3/providers/persistent.py +162 -3
- web3/providers/websocket/request_processor.py +40 -5
- web3/providers/websocket/websocket_v2.py +18 -114
- web3/tools/pytest_ethereum/deployer.py +1 -1
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/METADATA +1 -1
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/RECORD +14 -14
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/LICENSE +0 -0
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/WHEEL +0 -0
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/entry_points.txt +0 -0
- {web3-6.18.0.dist-info → web3-6.19.0.dist-info}/top_level.txt +0 -0
|
@@ -193,6 +193,12 @@ class WebsocketMessageStreamMock:
|
|
|
193
193
|
self.messages = deque(messages) if messages else deque()
|
|
194
194
|
self.raise_exception = raise_exception
|
|
195
195
|
|
|
196
|
+
def __await__(self) -> Generator[Any, Any, "Self"]:
|
|
197
|
+
async def __async_init__() -> "Self":
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
return __async_init__().__await__()
|
|
201
|
+
|
|
196
202
|
def __aiter__(self) -> "Self":
|
|
197
203
|
return self
|
|
198
204
|
|
|
@@ -205,6 +211,13 @@ class WebsocketMessageStreamMock:
|
|
|
205
211
|
|
|
206
212
|
return self.messages.popleft()
|
|
207
213
|
|
|
214
|
+
@staticmethod
|
|
215
|
+
async def pong() -> Literal[False]:
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
async def connect(self) -> None:
|
|
219
|
+
pass
|
|
220
|
+
|
|
208
221
|
async def send(self, data: bytes) -> None:
|
|
209
222
|
pass
|
|
210
223
|
|
web3/exceptions.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import time
|
|
3
3
|
from typing import (
|
|
4
|
+
TYPE_CHECKING,
|
|
4
5
|
Any,
|
|
5
6
|
Dict,
|
|
6
7
|
Optional,
|
|
@@ -15,6 +16,9 @@ from web3.types import (
|
|
|
15
16
|
BlockData,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
class Web3Exception(Exception):
|
|
20
24
|
"""
|
|
@@ -341,7 +345,21 @@ class BadResponseFormat(Web3Exception):
|
|
|
341
345
|
Raised when a JSON-RPC response comes back in an unexpected format
|
|
342
346
|
"""
|
|
343
347
|
|
|
344
|
-
|
|
348
|
+
|
|
349
|
+
class TaskNotRunning(Web3Exception):
|
|
350
|
+
"""
|
|
351
|
+
Used to signal between asyncio contexts that a task that is being awaited
|
|
352
|
+
is not currently running.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(
|
|
356
|
+
self, task: "asyncio.Task[Any]", message: Optional[str] = None
|
|
357
|
+
) -> None:
|
|
358
|
+
self.task = task
|
|
359
|
+
if message is None:
|
|
360
|
+
message = f"Task {task} is not running."
|
|
361
|
+
self.message = message
|
|
362
|
+
super().__init__(message)
|
|
345
363
|
|
|
346
364
|
|
|
347
365
|
class MethodUnavailable(Web3Exception):
|
web3/main.py
CHANGED
|
@@ -573,12 +573,9 @@ class _PersistentConnectionWeb3(AsyncWeb3):
|
|
|
573
573
|
|
|
574
574
|
# async for w3 in w3.persistent_websocket(provider)
|
|
575
575
|
async def __aiter__(self) -> AsyncIterator[Self]:
|
|
576
|
-
|
|
577
|
-
await self.provider.connect()
|
|
578
|
-
|
|
576
|
+
provider = self.provider
|
|
579
577
|
while True:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
continue
|
|
578
|
+
await provider.connect()
|
|
579
|
+
yield self
|
|
580
|
+
provider.logger.error("Connection interrupted, attempting to reconnect...")
|
|
581
|
+
await provider.disconnect()
|
web3/manager.py
CHANGED
|
@@ -35,6 +35,7 @@ from web3.exceptions import (
|
|
|
35
35
|
BadResponseFormat,
|
|
36
36
|
MethodUnavailable,
|
|
37
37
|
ProviderConnectionError,
|
|
38
|
+
TaskNotRunning,
|
|
38
39
|
)
|
|
39
40
|
from web3.middleware import (
|
|
40
41
|
abi_middleware,
|
|
@@ -377,14 +378,24 @@ class RequestManager:
|
|
|
377
378
|
raise ProviderConnectionError("No listener found for websocket connection.")
|
|
378
379
|
|
|
379
380
|
while True:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
381
|
+
try:
|
|
382
|
+
response = await self._request_processor.pop_raw_response(
|
|
383
|
+
subscription=True
|
|
384
|
+
)
|
|
385
|
+
if (
|
|
386
|
+
response is not None
|
|
387
|
+
and response.get("params", {}).get("subscription")
|
|
388
|
+
in self._request_processor.active_subscriptions
|
|
389
|
+
):
|
|
390
|
+
# if response is an active subscription response, process it
|
|
391
|
+
yield await self._process_ws_response(response)
|
|
392
|
+
except TaskNotRunning:
|
|
393
|
+
self._provider._handle_listener_task_exceptions()
|
|
394
|
+
self.logger.error(
|
|
395
|
+
"Message listener background task has stopped unexpectedly. "
|
|
396
|
+
"Stopping message stream."
|
|
397
|
+
)
|
|
398
|
+
raise StopAsyncIteration
|
|
388
399
|
|
|
389
400
|
async def _process_ws_response(self, response: RPCResponse) -> RPCResponse:
|
|
390
401
|
provider = cast(PersistentConnectionProvider, self._provider)
|
web3/providers/persistent.py
CHANGED
|
@@ -7,16 +7,30 @@ from typing import (
|
|
|
7
7
|
Optional,
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
from websockets
|
|
10
|
+
from websockets import (
|
|
11
|
+
ConnectionClosed,
|
|
11
12
|
WebSocketClientProtocol,
|
|
13
|
+
WebSocketException,
|
|
12
14
|
)
|
|
13
15
|
|
|
16
|
+
from web3._utils.caching import (
|
|
17
|
+
generate_cache_key,
|
|
18
|
+
)
|
|
19
|
+
from web3.exceptions import (
|
|
20
|
+
ProviderConnectionError,
|
|
21
|
+
TaskNotRunning,
|
|
22
|
+
TimeExhausted,
|
|
23
|
+
)
|
|
14
24
|
from web3.providers.async_base import (
|
|
15
25
|
AsyncJSONBaseProvider,
|
|
16
26
|
)
|
|
17
27
|
from web3.providers.websocket.request_processor import (
|
|
18
28
|
RequestProcessor,
|
|
19
29
|
)
|
|
30
|
+
from web3.types import (
|
|
31
|
+
RPCId,
|
|
32
|
+
RPCResponse,
|
|
33
|
+
)
|
|
20
34
|
|
|
21
35
|
DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 50.0
|
|
22
36
|
|
|
@@ -26,6 +40,8 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
26
40
|
has_persistent_connection = True
|
|
27
41
|
endpoint_uri: Optional[str] = None
|
|
28
42
|
|
|
43
|
+
_max_connection_retries: int = 5
|
|
44
|
+
|
|
29
45
|
_ws: Optional[WebSocketClientProtocol] = None
|
|
30
46
|
_request_processor: RequestProcessor
|
|
31
47
|
_message_listener_task: Optional["asyncio.Task[None]"] = None
|
|
@@ -36,6 +52,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
36
52
|
request_timeout: float = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT,
|
|
37
53
|
subscription_response_queue_size: int = 500,
|
|
38
54
|
request_information_cache_size: int = 500,
|
|
55
|
+
silence_listener_task_exceptions: bool = False,
|
|
39
56
|
) -> None:
|
|
40
57
|
super().__init__()
|
|
41
58
|
self._request_processor = RequestProcessor(
|
|
@@ -44,12 +61,154 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
|
44
61
|
request_information_cache_size=request_information_cache_size,
|
|
45
62
|
)
|
|
46
63
|
self.request_timeout = request_timeout
|
|
64
|
+
self.silence_listener_task_exceptions = silence_listener_task_exceptions
|
|
47
65
|
|
|
48
66
|
async def connect(self) -> None:
|
|
49
|
-
|
|
67
|
+
_connection_attempts = 0
|
|
68
|
+
_backoff_rate_change = 1.75
|
|
69
|
+
_backoff_time = 1.75
|
|
70
|
+
|
|
71
|
+
while _connection_attempts != self._max_connection_retries:
|
|
72
|
+
try:
|
|
73
|
+
_connection_attempts += 1
|
|
74
|
+
self.logger.info(f"Connecting to: {self.endpoint_uri}")
|
|
75
|
+
await self._provider_specific_connect()
|
|
76
|
+
self._message_listener_task = asyncio.create_task(
|
|
77
|
+
self._message_listener()
|
|
78
|
+
)
|
|
79
|
+
self._message_listener_task.add_done_callback(
|
|
80
|
+
self._message_listener_callback
|
|
81
|
+
)
|
|
82
|
+
self.logger.info(f"Successfully connected to: {self.endpoint_uri}")
|
|
83
|
+
break
|
|
84
|
+
except (WebSocketException, OSError) as e:
|
|
85
|
+
if _connection_attempts == self._max_connection_retries:
|
|
86
|
+
raise ProviderConnectionError(
|
|
87
|
+
f"Could not connect to: {self.endpoint_uri}. "
|
|
88
|
+
f"Retries exceeded max of {self._max_connection_retries}."
|
|
89
|
+
) from e
|
|
90
|
+
self.logger.info(
|
|
91
|
+
f"Could not connect to: {self.endpoint_uri}. "
|
|
92
|
+
f"Retrying in {round(_backoff_time, 1)} seconds.",
|
|
93
|
+
exc_info=True,
|
|
94
|
+
)
|
|
95
|
+
await asyncio.sleep(_backoff_time)
|
|
96
|
+
_backoff_time *= _backoff_rate_change
|
|
50
97
|
|
|
51
98
|
async def disconnect(self) -> None:
|
|
99
|
+
try:
|
|
100
|
+
if self._message_listener_task:
|
|
101
|
+
self._message_listener_task.cancel()
|
|
102
|
+
await self._message_listener_task
|
|
103
|
+
except (asyncio.CancelledError, StopAsyncIteration, ConnectionClosed):
|
|
104
|
+
pass
|
|
105
|
+
finally:
|
|
106
|
+
self._message_listener_task = None
|
|
107
|
+
self.logger.info("Message listener background task successfully shut down.")
|
|
108
|
+
|
|
109
|
+
await self._provider_specific_disconnect()
|
|
110
|
+
self._request_processor.clear_caches()
|
|
111
|
+
self.logger.info(f"Successfully disconnected from: {self.endpoint_uri}")
|
|
112
|
+
|
|
113
|
+
# -- private methods -- #
|
|
114
|
+
|
|
115
|
+
async def _provider_specific_connect(self) -> None:
|
|
52
116
|
raise NotImplementedError("Must be implemented by subclasses")
|
|
53
117
|
|
|
54
|
-
async def
|
|
118
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
55
119
|
raise NotImplementedError("Must be implemented by subclasses")
|
|
120
|
+
|
|
121
|
+
async def _provider_specific_message_listener(self) -> None:
|
|
122
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
123
|
+
|
|
124
|
+
def _message_listener_callback(
|
|
125
|
+
self, message_listener_task: "asyncio.Task[None]"
|
|
126
|
+
) -> None:
|
|
127
|
+
# Puts a `TaskNotRunning` in the queue to signal the end of the listener task
|
|
128
|
+
# to any running subscription streams that are awaiting a response.
|
|
129
|
+
self._request_processor._subscription_response_queue.put_nowait(
|
|
130
|
+
TaskNotRunning(message_listener_task)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
async def _message_listener(self) -> None:
|
|
134
|
+
self.logger.info(
|
|
135
|
+
f"{self.__class__.__qualname__} listener background task started. Storing "
|
|
136
|
+
"all messages in appropriate request processor queues / caches to be "
|
|
137
|
+
"processed."
|
|
138
|
+
)
|
|
139
|
+
while True:
|
|
140
|
+
# the use of sleep(0) seems to be the most efficient way to yield control
|
|
141
|
+
# back to the event loop to share the loop with other tasks.
|
|
142
|
+
await asyncio.sleep(0)
|
|
143
|
+
try:
|
|
144
|
+
await self._provider_specific_message_listener()
|
|
145
|
+
except Exception as e:
|
|
146
|
+
if not self.silence_listener_task_exceptions:
|
|
147
|
+
raise e
|
|
148
|
+
else:
|
|
149
|
+
self._error_log_listener_task_exception(e)
|
|
150
|
+
|
|
151
|
+
def _error_log_listener_task_exception(self, e: Exception) -> None:
|
|
152
|
+
"""
|
|
153
|
+
When silencing listener task exceptions, this method is used to log the
|
|
154
|
+
exception and keep the listener task alive. Override this method to fine-tune
|
|
155
|
+
error logging behavior for the implementation class.
|
|
156
|
+
"""
|
|
157
|
+
self.logger.error(
|
|
158
|
+
"Exception caught in listener, error logging and keeping "
|
|
159
|
+
"listener background task alive."
|
|
160
|
+
f"\n error={e.__class__.__name__}: {e}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _handle_listener_task_exceptions(self) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Should be called every time a `PersistentConnectionProvider` is polling for
|
|
166
|
+
messages in the main loop. If the message listener task has completed and an
|
|
167
|
+
exception was recorded, raise the exception in the main loop.
|
|
168
|
+
"""
|
|
169
|
+
msg_listener_task = getattr(self, "_message_listener_task", None)
|
|
170
|
+
if (
|
|
171
|
+
msg_listener_task
|
|
172
|
+
and msg_listener_task.done()
|
|
173
|
+
and msg_listener_task.exception()
|
|
174
|
+
):
|
|
175
|
+
raise msg_listener_task.exception()
|
|
176
|
+
|
|
177
|
+
async def _get_response_for_request_id(
|
|
178
|
+
self, request_id: RPCId, timeout: Optional[float] = None
|
|
179
|
+
) -> RPCResponse:
|
|
180
|
+
if timeout is None:
|
|
181
|
+
timeout = self.request_timeout
|
|
182
|
+
|
|
183
|
+
async def _match_response_id_to_request_id() -> RPCResponse:
|
|
184
|
+
request_cache_key = generate_cache_key(request_id)
|
|
185
|
+
|
|
186
|
+
while True:
|
|
187
|
+
# check if an exception was recorded in the listener task and raise it
|
|
188
|
+
# in the main loop if so
|
|
189
|
+
self._handle_listener_task_exceptions()
|
|
190
|
+
|
|
191
|
+
if request_cache_key in self._request_processor._request_response_cache:
|
|
192
|
+
self.logger.debug(
|
|
193
|
+
f"Popping response for id {request_id} from cache."
|
|
194
|
+
)
|
|
195
|
+
popped_response = await self._request_processor.pop_raw_response(
|
|
196
|
+
cache_key=request_cache_key,
|
|
197
|
+
)
|
|
198
|
+
return popped_response
|
|
199
|
+
else:
|
|
200
|
+
await asyncio.sleep(0)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# Add the request timeout around the while loop that checks the request
|
|
204
|
+
# cache. If the request is not in the cache within the request_timeout,
|
|
205
|
+
# raise ``TimeExhausted``.
|
|
206
|
+
return await asyncio.wait_for(_match_response_id_to_request_id(), timeout)
|
|
207
|
+
except asyncio.TimeoutError:
|
|
208
|
+
raise TimeExhausted(
|
|
209
|
+
f"Timed out waiting for response with request id `{request_id}` after "
|
|
210
|
+
f"{self.request_timeout} second(s). This may be due to the provider "
|
|
211
|
+
"not returning a response with the same id that was sent in the "
|
|
212
|
+
"request or an exception raised during the request was caught and "
|
|
213
|
+
"allowed to continue."
|
|
214
|
+
)
|
|
@@ -2,19 +2,26 @@ import asyncio
|
|
|
2
2
|
from copy import (
|
|
3
3
|
copy,
|
|
4
4
|
)
|
|
5
|
+
import sys
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
7
8
|
Any,
|
|
8
9
|
Callable,
|
|
9
10
|
Dict,
|
|
11
|
+
Generic,
|
|
10
12
|
Optional,
|
|
11
13
|
Tuple,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
12
16
|
)
|
|
13
17
|
|
|
14
18
|
from web3._utils.caching import (
|
|
15
19
|
RequestInformation,
|
|
16
20
|
generate_cache_key,
|
|
17
21
|
)
|
|
22
|
+
from web3.exceptions import (
|
|
23
|
+
TaskNotRunning,
|
|
24
|
+
)
|
|
18
25
|
from web3.types import (
|
|
19
26
|
RPCEndpoint,
|
|
20
27
|
RPCResponse,
|
|
@@ -28,6 +35,34 @@ if TYPE_CHECKING:
|
|
|
28
35
|
PersistentConnectionProvider,
|
|
29
36
|
)
|
|
30
37
|
|
|
38
|
+
T = TypeVar("T")
|
|
39
|
+
|
|
40
|
+
# TODO: This is an ugly hack for python 3.8. Remove this after we drop support for it
|
|
41
|
+
# and use `asyncio.Queue[T]` type directly in the `TaskReliantQueue` class.
|
|
42
|
+
if sys.version_info >= (3, 9):
|
|
43
|
+
|
|
44
|
+
class _TaskReliantQueue(asyncio.Queue[T], Generic[T]):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
else:
|
|
48
|
+
|
|
49
|
+
class _TaskReliantQueue(asyncio.Queue, Generic[T]): # type: ignore
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TaskReliantQueue(_TaskReliantQueue[T]):
|
|
54
|
+
"""
|
|
55
|
+
A queue that relies on a task to be running to process items in the queue.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
async def get(self) -> T:
|
|
59
|
+
item = await super().get()
|
|
60
|
+
if isinstance(item, Exception):
|
|
61
|
+
# if the item is an exception, raise it so the task can handle this case
|
|
62
|
+
# more gracefully
|
|
63
|
+
raise item
|
|
64
|
+
return item
|
|
65
|
+
|
|
31
66
|
|
|
32
67
|
class RequestProcessor:
|
|
33
68
|
_subscription_queue_synced_with_ws_stream: bool = False
|
|
@@ -41,9 +76,9 @@ class RequestProcessor:
|
|
|
41
76
|
self._provider = provider
|
|
42
77
|
|
|
43
78
|
self._request_response_cache: SimpleCache = SimpleCache(500)
|
|
44
|
-
self._subscription_response_queue:
|
|
45
|
-
|
|
46
|
-
)
|
|
79
|
+
self._subscription_response_queue: TaskReliantQueue[
|
|
80
|
+
Union[RPCResponse, TaskNotRunning]
|
|
81
|
+
] = TaskReliantQueue(maxsize=subscription_response_queue_size)
|
|
47
82
|
self._request_information_cache: SimpleCache = SimpleCache(
|
|
48
83
|
request_information_cache_size
|
|
49
84
|
)
|
|
@@ -203,7 +238,7 @@ class RequestProcessor:
|
|
|
203
238
|
) -> None:
|
|
204
239
|
if subscription:
|
|
205
240
|
if self._subscription_response_queue.full():
|
|
206
|
-
self._provider.logger.
|
|
241
|
+
self._provider.logger.debug(
|
|
207
242
|
"Subscription queue is full. Waiting for provider to consume "
|
|
208
243
|
"messages before caching."
|
|
209
244
|
)
|
|
@@ -276,6 +311,6 @@ class RequestProcessor:
|
|
|
276
311
|
|
|
277
312
|
self._request_information_cache.clear()
|
|
278
313
|
self._request_response_cache.clear()
|
|
279
|
-
self._subscription_response_queue =
|
|
314
|
+
self._subscription_response_queue = TaskReliantQueue(
|
|
280
315
|
maxsize=self._subscription_response_queue.maxsize
|
|
281
316
|
)
|
|
@@ -22,12 +22,8 @@ from websockets.exceptions import (
|
|
|
22
22
|
WebSocketException,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
from web3._utils.caching import (
|
|
26
|
-
generate_cache_key,
|
|
27
|
-
)
|
|
28
25
|
from web3.exceptions import (
|
|
29
26
|
ProviderConnectionError,
|
|
30
|
-
TimeExhausted,
|
|
31
27
|
Web3ValidationError,
|
|
32
28
|
)
|
|
33
29
|
from web3.providers.persistent import (
|
|
@@ -35,7 +31,6 @@ from web3.providers.persistent import (
|
|
|
35
31
|
)
|
|
36
32
|
from web3.types import (
|
|
37
33
|
RPCEndpoint,
|
|
38
|
-
RPCId,
|
|
39
34
|
RPCResponse,
|
|
40
35
|
)
|
|
41
36
|
|
|
@@ -59,7 +54,6 @@ def get_default_endpoint() -> URI:
|
|
|
59
54
|
class WebsocketProviderV2(PersistentConnectionProvider):
|
|
60
55
|
logger = logging.getLogger("web3.providers.WebsocketProviderV2")
|
|
61
56
|
is_async: bool = True
|
|
62
|
-
_max_connection_retries: int = 5
|
|
63
57
|
|
|
64
58
|
def __init__(
|
|
65
59
|
self,
|
|
@@ -69,16 +63,16 @@ class WebsocketProviderV2(PersistentConnectionProvider):
|
|
|
69
63
|
# `PersistentConnectionProvider` kwargs can be passed through
|
|
70
64
|
**kwargs: Any,
|
|
71
65
|
) -> None:
|
|
72
|
-
self.endpoint_uri =
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
self.endpoint_uri = (
|
|
67
|
+
URI(endpoint_uri) if endpoint_uri is not None else get_default_endpoint()
|
|
68
|
+
)
|
|
75
69
|
|
|
76
70
|
if not any(
|
|
77
71
|
self.endpoint_uri.startswith(prefix)
|
|
78
72
|
for prefix in VALID_WEBSOCKET_URI_PREFIXES
|
|
79
73
|
):
|
|
80
74
|
raise Web3ValidationError(
|
|
81
|
-
"
|
|
75
|
+
"WebSocket endpoint uri must begin with 'ws://' or 'wss://': "
|
|
82
76
|
f"{self.endpoint_uri}"
|
|
83
77
|
)
|
|
84
78
|
|
|
@@ -93,12 +87,13 @@ class WebsocketProviderV2(PersistentConnectionProvider):
|
|
|
93
87
|
)
|
|
94
88
|
|
|
95
89
|
self.websocket_kwargs = merge(DEFAULT_WEBSOCKET_KWARGS, websocket_kwargs or {})
|
|
96
|
-
self.silence_listener_task_exceptions = silence_listener_task_exceptions
|
|
97
90
|
|
|
98
|
-
super().__init__(
|
|
91
|
+
super().__init__(
|
|
92
|
+
silence_listener_task_exceptions=silence_listener_task_exceptions, **kwargs
|
|
93
|
+
)
|
|
99
94
|
|
|
100
95
|
def __str__(self) -> str:
|
|
101
|
-
return f"
|
|
96
|
+
return f"WebSocket connection: {self.endpoint_uri}"
|
|
102
97
|
|
|
103
98
|
async def is_connected(self, show_traceback: bool = False) -> bool:
|
|
104
99
|
if not self._ws:
|
|
@@ -115,47 +110,13 @@ class WebsocketProviderV2(PersistentConnectionProvider):
|
|
|
115
110
|
) from e
|
|
116
111
|
return False
|
|
117
112
|
|
|
118
|
-
async def
|
|
119
|
-
|
|
120
|
-
_backoff_rate_change = 1.75
|
|
121
|
-
_backoff_time = 1.75
|
|
122
|
-
|
|
123
|
-
while _connection_attempts != self._max_connection_retries:
|
|
124
|
-
try:
|
|
125
|
-
_connection_attempts += 1
|
|
126
|
-
self._ws = await connect(self.endpoint_uri, **self.websocket_kwargs)
|
|
127
|
-
self._message_listener_task = asyncio.create_task(
|
|
128
|
-
self._ws_message_listener()
|
|
129
|
-
)
|
|
130
|
-
break
|
|
131
|
-
except WebSocketException as e:
|
|
132
|
-
if _connection_attempts == self._max_connection_retries:
|
|
133
|
-
raise ProviderConnectionError(
|
|
134
|
-
f"Could not connect to endpoint: {self.endpoint_uri}. "
|
|
135
|
-
f"Retries exceeded max of {self._max_connection_retries}."
|
|
136
|
-
) from e
|
|
137
|
-
self.logger.info(
|
|
138
|
-
f"Could not connect to endpoint: {self.endpoint_uri}. Retrying in "
|
|
139
|
-
f"{round(_backoff_time, 1)} seconds.",
|
|
140
|
-
exc_info=True,
|
|
141
|
-
)
|
|
142
|
-
await asyncio.sleep(_backoff_time)
|
|
143
|
-
_backoff_time *= _backoff_rate_change
|
|
113
|
+
async def _provider_specific_connect(self) -> None:
|
|
114
|
+
self._ws = await connect(self.endpoint_uri, **self.websocket_kwargs)
|
|
144
115
|
|
|
145
|
-
async def
|
|
116
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
146
117
|
if self._ws is not None and not self._ws.closed:
|
|
147
118
|
await self._ws.close()
|
|
148
119
|
self._ws = None
|
|
149
|
-
self.logger.debug(
|
|
150
|
-
f'Successfully disconnected from endpoint: "{self.endpoint_uri}'
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
self._message_listener_task.cancel()
|
|
155
|
-
await self._message_listener_task
|
|
156
|
-
except (asyncio.CancelledError, StopAsyncIteration):
|
|
157
|
-
pass
|
|
158
|
-
self._request_processor.clear_caches()
|
|
159
120
|
|
|
160
121
|
async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
161
122
|
request_data = self.encode_rpc_request(method, params)
|
|
@@ -174,69 +135,12 @@ class WebsocketProviderV2(PersistentConnectionProvider):
|
|
|
174
135
|
|
|
175
136
|
return response
|
|
176
137
|
|
|
177
|
-
async def
|
|
178
|
-
async
|
|
179
|
-
request_cache_key = generate_cache_key(request_id)
|
|
180
|
-
|
|
181
|
-
while True:
|
|
182
|
-
# sleep(0) here seems to be the most efficient way to yield control
|
|
183
|
-
# back to the event loop while waiting for the response to be in the
|
|
184
|
-
# queue.
|
|
185
|
-
await asyncio.sleep(0)
|
|
186
|
-
|
|
187
|
-
if request_cache_key in self._request_processor._request_response_cache:
|
|
188
|
-
self.logger.debug(
|
|
189
|
-
f"Popping response for id {request_id} from cache."
|
|
190
|
-
)
|
|
191
|
-
popped_response = await self._request_processor.pop_raw_response(
|
|
192
|
-
cache_key=request_cache_key,
|
|
193
|
-
)
|
|
194
|
-
return popped_response
|
|
195
|
-
|
|
196
|
-
try:
|
|
197
|
-
# Add the request timeout around the while loop that checks the request
|
|
198
|
-
# cache and tried to recv(). If the request is neither in the cache, nor
|
|
199
|
-
# received within the request_timeout, raise ``TimeExhausted``.
|
|
200
|
-
return await asyncio.wait_for(
|
|
201
|
-
_match_response_id_to_request_id(), self.request_timeout
|
|
202
|
-
)
|
|
203
|
-
except asyncio.TimeoutError:
|
|
204
|
-
raise TimeExhausted(
|
|
205
|
-
f"Timed out waiting for response with request id `{request_id}` after "
|
|
206
|
-
f"{self.request_timeout} second(s). This may be due to the provider "
|
|
207
|
-
"not returning a response with the same id that was sent in the "
|
|
208
|
-
"request or an exception raised during the request was caught and "
|
|
209
|
-
"allowed to continue."
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
async def _ws_message_listener(self) -> None:
|
|
213
|
-
self.logger.info(
|
|
214
|
-
"Websocket listener background task started. Storing all messages in "
|
|
215
|
-
"appropriate request processor queues / caches to be processed."
|
|
216
|
-
)
|
|
217
|
-
while True:
|
|
218
|
-
# the use of sleep(0) seems to be the most efficient way to yield control
|
|
219
|
-
# back to the event loop to share the loop with other tasks.
|
|
138
|
+
async def _provider_specific_message_listener(self) -> None:
|
|
139
|
+
async for raw_message in self._ws:
|
|
220
140
|
await asyncio.sleep(0)
|
|
221
141
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
subscription = response.get("method") == "eth_subscription"
|
|
228
|
-
await self._request_processor.cache_raw_response(
|
|
229
|
-
response, subscription=subscription
|
|
230
|
-
)
|
|
231
|
-
except Exception as e:
|
|
232
|
-
if not self.silence_listener_task_exceptions:
|
|
233
|
-
loop = asyncio.get_event_loop()
|
|
234
|
-
for task in asyncio.all_tasks(loop=loop):
|
|
235
|
-
task.cancel()
|
|
236
|
-
raise e
|
|
237
|
-
|
|
238
|
-
self.logger.error(
|
|
239
|
-
"Exception caught in listener, error logging and keeping "
|
|
240
|
-
"listener background task alive."
|
|
241
|
-
f"\n error={e.__class__.__name__}: {e}"
|
|
242
|
-
)
|
|
142
|
+
response = json.loads(raw_message)
|
|
143
|
+
subscription = response.get("method") == "eth_subscription"
|
|
144
|
+
await self._request_processor.cache_raw_response(
|
|
145
|
+
response, subscription=subscription
|
|
146
|
+
)
|
|
@@ -27,7 +27,7 @@ class Deployer:
|
|
|
27
27
|
f"Expected a Package object, instead received {type(package)}."
|
|
28
28
|
)
|
|
29
29
|
self.package = package
|
|
30
|
-
self.strategies
|
|
30
|
+
self.strategies: Dict[str, Callable[[Package], Package]] = {}
|
|
31
31
|
|
|
32
32
|
def deploy(self, contract_type: ContractName, *args: Any, **kwargs: Any) -> Package:
|
|
33
33
|
factory = self.package.get_contract_factory(contract_type)
|
|
@@ -117,11 +117,11 @@ ethpm/validation/uri.py,sha256=JtNfDghEU-8yDoETIbEZnamlpFGF1fAQ-tdJQhqn7mg,4873
|
|
|
117
117
|
web3/__init__.py,sha256=tieKn-7-iyxdwx_tI7eUGWBpSkawfI2_3pCnwh8PRP4,972
|
|
118
118
|
web3/constants.py,sha256=eQLRQVMFPbgpOjjkPTMHkY-syncJuO-sPX5UrCSRjzQ,564
|
|
119
119
|
web3/datastructures.py,sha256=5SuX36p-hGuiK3RwnG8yVsgROvkqzHdDKfTGSzghSy0,9234
|
|
120
|
-
web3/exceptions.py,sha256=
|
|
120
|
+
web3/exceptions.py,sha256=3c0uRBiKvRJCSQUALYDCjjsjcyqx_XJM4_oQBXKrBrs,7390
|
|
121
121
|
web3/geth.py,sha256=IXdSkRg5n8Nv6KXdY94HwEcFnixOosOHoNanvJNA5rk,13676
|
|
122
122
|
web3/logs.py,sha256=ROs-mDMH_ZOecE7hfbWA5yp27G38FbLjX4lO_WtlZxQ,198
|
|
123
|
-
web3/main.py,sha256=
|
|
124
|
-
web3/manager.py,sha256=
|
|
123
|
+
web3/main.py,sha256=bPMEBxLfoA4VeLIeAO4V7oXXujHieBQHpDTA3eMHf_I,15854
|
|
124
|
+
web3/manager.py,sha256=TI705zxN2ZEnqJoA3EwWoiYnnw9B642c9rHndIg0BtA,16643
|
|
125
125
|
web3/method.py,sha256=KKtQR9YjzSWWzl_gsLX0P4JqOEJnCzhN6naM9GOQvm0,8349
|
|
126
126
|
web3/module.py,sha256=hoPoBaH992SHm_DRA7yxYZTFy61RQJ7_NZTU-AkZmds,4659
|
|
127
127
|
web3/net.py,sha256=Y3vPzHWVFkfHEZoJxjDOt4tp5ERmZrMuyi4ZFOLmIeA,1562
|
|
@@ -195,7 +195,7 @@ web3/_utils/module_testing/eth_module.py,sha256=JQ0XAF5GlvpHn3oeAi2DfltJdCZwHk7J
|
|
|
195
195
|
web3/_utils/module_testing/go_ethereum_admin_module.py,sha256=_c-6SyzZkfAJ-7ySXUpw9FEr4cg-ShRdAGSAHWanCtY,3406
|
|
196
196
|
web3/_utils/module_testing/go_ethereum_personal_module.py,sha256=KzYEcAs_6Ud_gbu6YMOC6HijTdNFiz-qogMCViedmY0,11536
|
|
197
197
|
web3/_utils/module_testing/go_ethereum_txpool_module.py,sha256=5f8XL8-2x3keyGRaITxMQYl9oQzjgqGn8zobB-j9BPs,1176
|
|
198
|
-
web3/_utils/module_testing/module_testing_utils.py,sha256=
|
|
198
|
+
web3/_utils/module_testing/module_testing_utils.py,sha256=_BV5xITzHC6HkOL3GD6bCJ-vhEsPQt2iSpY9S_SWXYo,6290
|
|
199
199
|
web3/_utils/module_testing/net_module.py,sha256=ifUTC-5fTcQbwpm0X09OdD5RSPnn00T8klFeYe8tTm4,1272
|
|
200
200
|
web3/_utils/module_testing/persistent_connection_provider.py,sha256=2NFjp0ZShOt_D5gmcfrc2ua57mRyeFSZpkqtPgCNUgM,16620
|
|
201
201
|
web3/_utils/module_testing/web3_module.py,sha256=Y4lYglg_kbrpfwGfjekv52h4B7DaWa3uS15KGrCKL7c,9613
|
|
@@ -243,17 +243,17 @@ web3/providers/async_rpc.py,sha256=1tf72qPH8A3IW2ZJus9nORL-PfPgRtmj1YRFASVWyuM,2
|
|
|
243
243
|
web3/providers/auto.py,sha256=-dS_-2nhg2jOA432P0w3Ul3NUIso8rTl4uGUGo0XBw0,3447
|
|
244
244
|
web3/providers/base.py,sha256=qASmfdhXhD2FSk2Uk8z-8NxVL4i42uJNoUlwRDzQpgI,4187
|
|
245
245
|
web3/providers/ipc.py,sha256=xFWT6cY9emTnFMuIQFrmQ0DJMQSMoQtaztD7MTTVniI,6374
|
|
246
|
-
web3/providers/persistent.py,sha256=
|
|
246
|
+
web3/providers/persistent.py,sha256=L9Cgs3RxcUmplo3-mnDl9U0VlzMWfXZSVMWtWMjS0bY,8376
|
|
247
247
|
web3/providers/rpc.py,sha256=w3XA8lhufp_tcGCqocRwi2DDt8j3fj0hoZGk0dfF0u0,2686
|
|
248
248
|
web3/providers/eth_tester/__init__.py,sha256=J6wazY3hQySsUfpFgwCXbEMo-CGEZjRo11RCg4UXKmA,83
|
|
249
249
|
web3/providers/eth_tester/defaults.py,sha256=Ri_WPJT8aumxwiZXwedkJ16H_gBkLsKqanlDLfeFqM8,14803
|
|
250
250
|
web3/providers/eth_tester/main.py,sha256=mI7XAqp0_lZVtVhr7HAh8noJfcxYL9qFfmxoV2jKVPo,5845
|
|
251
251
|
web3/providers/eth_tester/middleware.py,sha256=LN3UZ789e5pwfBBrx9_1N2ByiEYIy6ioMpYddv1u1Zw,13944
|
|
252
252
|
web3/providers/websocket/__init__.py,sha256=W5xi6NxUVaskom7k03vd2HCXSH8hXAob2ry-ULiNSUo,232
|
|
253
|
-
web3/providers/websocket/request_processor.py,sha256=
|
|
253
|
+
web3/providers/websocket/request_processor.py,sha256=kir6bkAWy6CQdBpNrk_8CB8GPsjXM42SXx7IYlAngqE,11653
|
|
254
254
|
web3/providers/websocket/websocket.py,sha256=-U2-CGtqpysxZoFHsa-rBeZKjSRejqQSeb5Z12SzKwk,3929
|
|
255
255
|
web3/providers/websocket/websocket_connection.py,sha256=_W3i_nF9NNRdSwFGwLsDCNXrrhlhV-NvmywX8vbuWvY,1104
|
|
256
|
-
web3/providers/websocket/websocket_v2.py,sha256=
|
|
256
|
+
web3/providers/websocket/websocket_v2.py,sha256=HX-p1CATh2eA02H0vbKh33XQdahoiSp5RH8uUoXzndY,4402
|
|
257
257
|
web3/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
258
258
|
web3/scripts/release/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
259
259
|
web3/scripts/release/test_package.py,sha256=DH0AryllcF4zfpWSd0NLPSQGHNoC-Qng5WYYbS5_4c8,1534
|
|
@@ -265,7 +265,7 @@ web3/tools/benchmark/reporting.py,sha256=t6XZhitwMcKjIXU6tlPEjgDmozvCSK3RoOW-0bO
|
|
|
265
265
|
web3/tools/benchmark/utils.py,sha256=GjSofFEq3ugFtpyBlAx7n6cgZrQ5Xu4-hN_36bQBwnA,1722
|
|
266
266
|
web3/tools/pytest_ethereum/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
267
267
|
web3/tools/pytest_ethereum/_utils.py,sha256=2rZz6L4lXmpTyXFiXWFu8AI2qX4sftzjlbdtqmaWgGE,4158
|
|
268
|
-
web3/tools/pytest_ethereum/deployer.py,sha256=
|
|
268
|
+
web3/tools/pytest_ethereum/deployer.py,sha256=xAlBX7Mvn6csumjrCItDf8iPLfLwj2OsuAshPdnRPZI,1415
|
|
269
269
|
web3/tools/pytest_ethereum/exceptions.py,sha256=5H8abL5ywZlSR0ASCR_2klPUDKjncf-7BTVgAcFraUU,380
|
|
270
270
|
web3/tools/pytest_ethereum/linker.py,sha256=fnkHsX2Yhus29nnPj0bTCkWdzTUeSfvxYIamM9QcqI8,3927
|
|
271
271
|
web3/tools/pytest_ethereum/plugins.py,sha256=QgC4Lm-pHIAj5IMFcmKyuWa13_mfedkSVmjuE_8Tdzc,645
|
|
@@ -275,9 +275,9 @@ web3/utils/address.py,sha256=KC_IpEbixSCuMhaW6V2QCyyJTYKYGS9c8QtI9_aH7zQ,967
|
|
|
275
275
|
web3/utils/async_exception_handling.py,sha256=gfLuzP7Y5rc21jZVjWEYAOZUMJkJd9CmsL297UKReow,3096
|
|
276
276
|
web3/utils/caching.py,sha256=PgfuXiVgPYQuS0S89-jkNOJ3L2uTRt2MkVBvzMkaAXI,1770
|
|
277
277
|
web3/utils/exception_handling.py,sha256=12xkzIqMAOx0Jcm6PYL98PmWlLPKFll0p9YoLGS_ZNg,3052
|
|
278
|
-
web3-6.
|
|
279
|
-
web3-6.
|
|
280
|
-
web3-6.
|
|
281
|
-
web3-6.
|
|
282
|
-
web3-6.
|
|
283
|
-
web3-6.
|
|
278
|
+
web3-6.19.0.dist-info/LICENSE,sha256=ulnXiEqqFp9VyWe8yMFdtDi70RMBJk3mpY3FKujv6l8,1090
|
|
279
|
+
web3-6.19.0.dist-info/METADATA,sha256=x0ZZ5THuZWBU5i5JndaNJpnoYMemRUafzsdYa5bVOaA,4522
|
|
280
|
+
web3-6.19.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
281
|
+
web3-6.19.0.dist-info/entry_points.txt,sha256=2qjzGxFUlYBzoP68fcB3AJyMRunWI70uBoxNp17Brb0,64
|
|
282
|
+
web3-6.19.0.dist-info/top_level.txt,sha256=5lRZg30BFUrz8eUK60C7OAjNT3FI4YsGmA-vZ0WIOik,15
|
|
283
|
+
web3-6.19.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|