web3 7.0.0b5__py3-none-any.whl → 7.0.0b6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- web3/_utils/batching.py +217 -0
- web3/_utils/caching.py +26 -2
- web3/_utils/compat/__init__.py +1 -0
- web3/_utils/events.py +2 -2
- web3/_utils/module_testing/eth_module.py +10 -10
- web3/_utils/module_testing/module_testing_utils.py +13 -0
- web3/_utils/module_testing/web3_module.py +438 -17
- web3/contract/utils.py +112 -4
- web3/eth/async_eth.py +5 -4
- web3/eth/eth.py +5 -4
- web3/exceptions.py +20 -0
- web3/gas_strategies/time_based.py +2 -2
- web3/main.py +21 -9
- web3/manager.py +113 -8
- web3/method.py +29 -9
- web3/middleware/base.py +43 -0
- web3/module.py +47 -7
- web3/providers/async_base.py +55 -23
- web3/providers/base.py +59 -26
- web3/providers/ipc.py +23 -8
- web3/providers/legacy_websocket.py +26 -1
- web3/providers/persistent/async_ipc.py +60 -76
- web3/providers/persistent/persistent.py +134 -10
- web3/providers/persistent/request_processor.py +98 -14
- web3/providers/persistent/websocket.py +43 -66
- web3/providers/rpc/async_rpc.py +19 -1
- web3/providers/rpc/rpc.py +19 -1
- web3/types.py +7 -1
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/LICENSE +1 -1
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/METADATA +31 -20
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/RECORD +33 -32
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/WHEEL +0 -0
- {web3-7.0.0b5.dist-info → web3-7.0.0b6.dist-info}/top_level.txt +0 -0
web3/providers/async_base.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import (
|
|
|
5
5
|
Any,
|
|
6
6
|
Callable,
|
|
7
7
|
Coroutine,
|
|
8
|
+
List,
|
|
8
9
|
Optional,
|
|
9
10
|
Set,
|
|
10
11
|
Tuple,
|
|
@@ -18,6 +19,7 @@ from eth_utils import (
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
from web3._utils.caching import (
|
|
22
|
+
CACHEABLE_REQUESTS,
|
|
21
23
|
async_handle_request_caching,
|
|
22
24
|
)
|
|
23
25
|
from web3._utils.encoding import (
|
|
@@ -56,41 +58,33 @@ if TYPE_CHECKING:
|
|
|
56
58
|
)
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
CACHEABLE_REQUESTS = cast(
|
|
60
|
-
Set[RPCEndpoint],
|
|
61
|
-
(
|
|
62
|
-
"eth_chainId",
|
|
63
|
-
"eth_getBlockByHash",
|
|
64
|
-
"eth_getBlockTransactionCountByHash",
|
|
65
|
-
"eth_getRawTransactionByHash",
|
|
66
|
-
"eth_getTransactionByBlockHashAndIndex",
|
|
67
|
-
"eth_getTransactionByHash",
|
|
68
|
-
"eth_getUncleByBlockHashAndIndex",
|
|
69
|
-
"eth_getUncleCountByBlockHash",
|
|
70
|
-
"net_version",
|
|
71
|
-
"web3_clientVersion",
|
|
72
|
-
),
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
61
|
class AsyncBaseProvider:
|
|
77
62
|
_request_func_cache: Tuple[
|
|
78
63
|
Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, RPCResponse]]
|
|
79
64
|
] = (None, None)
|
|
80
65
|
|
|
66
|
+
_is_batching: bool = False
|
|
67
|
+
_batch_request_func_cache: Tuple[
|
|
68
|
+
Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, List[RPCResponse]]]
|
|
69
|
+
] = (None, None)
|
|
70
|
+
|
|
81
71
|
is_async = True
|
|
82
72
|
has_persistent_connection = False
|
|
83
73
|
global_ccip_read_enabled: bool = True
|
|
84
74
|
ccip_read_max_redirects: int = 4
|
|
85
75
|
|
|
86
76
|
# request caching
|
|
87
|
-
cache_allowed_requests: bool = False
|
|
88
|
-
cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
|
|
89
77
|
_request_cache: SimpleCache
|
|
90
78
|
_request_cache_lock: asyncio.Lock = asyncio.Lock()
|
|
91
79
|
|
|
92
|
-
def __init__(
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
cache_allowed_requests: bool = False,
|
|
83
|
+
cacheable_requests: Set[RPCEndpoint] = None,
|
|
84
|
+
) -> None:
|
|
93
85
|
self._request_cache = SimpleCache(1000)
|
|
86
|
+
self.cache_allowed_requests = cache_allowed_requests
|
|
87
|
+
self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
|
|
94
88
|
|
|
95
89
|
async def request_func(
|
|
96
90
|
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
@@ -109,10 +103,34 @@ class AsyncBaseProvider:
|
|
|
109
103
|
)
|
|
110
104
|
return self._request_func_cache[-1]
|
|
111
105
|
|
|
106
|
+
async def batch_request_func(
|
|
107
|
+
self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
|
|
108
|
+
) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
|
|
109
|
+
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
110
|
+
|
|
111
|
+
cache_key = self._batch_request_func_cache[0]
|
|
112
|
+
if cache_key != middleware:
|
|
113
|
+
accumulator_fn = self.make_batch_request
|
|
114
|
+
for mw in reversed(middleware):
|
|
115
|
+
initialized = mw(async_w3)
|
|
116
|
+
# type ignore bc in order to wrap the method, we have to call
|
|
117
|
+
# `async_wrap_make_batch_request` with the accumulator_fn as the
|
|
118
|
+
# argument which breaks the type hinting for this particular case.
|
|
119
|
+
accumulator_fn = await initialized.async_wrap_make_batch_request( # type: ignore # noqa: E501
|
|
120
|
+
accumulator_fn
|
|
121
|
+
)
|
|
122
|
+
self._batch_request_func_cache = (middleware, accumulator_fn)
|
|
123
|
+
return self._batch_request_func_cache[-1]
|
|
124
|
+
|
|
112
125
|
@async_handle_request_caching
|
|
113
126
|
async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
114
127
|
raise NotImplementedError("Providers must implement this method")
|
|
115
128
|
|
|
129
|
+
async def make_batch_request(
|
|
130
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
131
|
+
) -> List[RPCResponse]:
|
|
132
|
+
raise NotImplementedError("Only AsyncHTTPProvider supports this method")
|
|
133
|
+
|
|
116
134
|
async def is_connected(self, show_traceback: bool = False) -> bool:
|
|
117
135
|
raise NotImplementedError("Providers must implement this method")
|
|
118
136
|
|
|
@@ -141,9 +159,9 @@ class AsyncBaseProvider:
|
|
|
141
159
|
|
|
142
160
|
|
|
143
161
|
class AsyncJSONBaseProvider(AsyncBaseProvider):
|
|
144
|
-
def __init__(self) -> None:
|
|
145
|
-
super().__init__()
|
|
162
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
146
163
|
self.request_counter = itertools.count()
|
|
164
|
+
super().__init__(**kwargs)
|
|
147
165
|
|
|
148
166
|
def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
|
|
149
167
|
request_id = next(self.request_counter)
|
|
@@ -156,7 +174,8 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
|
|
|
156
174
|
encoded = FriendlyJsonSerde().json_encode(rpc_dict, cls=Web3JsonEncoder)
|
|
157
175
|
return to_bytes(text=encoded)
|
|
158
176
|
|
|
159
|
-
|
|
177
|
+
@staticmethod
|
|
178
|
+
def decode_rpc_response(raw_response: bytes) -> RPCResponse:
|
|
160
179
|
text_response = str(
|
|
161
180
|
to_text(raw_response) if not is_text(raw_response) else raw_response
|
|
162
181
|
)
|
|
@@ -185,3 +204,16 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
|
|
|
185
204
|
if show_traceback:
|
|
186
205
|
raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
|
|
187
206
|
return False
|
|
207
|
+
|
|
208
|
+
# -- batch requests -- #
|
|
209
|
+
|
|
210
|
+
def encode_batch_rpc_request(
|
|
211
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
212
|
+
) -> bytes:
|
|
213
|
+
return (
|
|
214
|
+
b"["
|
|
215
|
+
+ b", ".join(
|
|
216
|
+
self.encode_rpc_request(method, params) for method, params in requests
|
|
217
|
+
)
|
|
218
|
+
+ b"]"
|
|
219
|
+
)
|
web3/providers/base.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import (
|
|
|
4
4
|
TYPE_CHECKING,
|
|
5
5
|
Any,
|
|
6
6
|
Callable,
|
|
7
|
+
List,
|
|
7
8
|
Set,
|
|
8
9
|
Tuple,
|
|
9
10
|
cast,
|
|
@@ -15,6 +16,7 @@ from eth_utils import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from web3._utils.caching import (
|
|
19
|
+
CACHEABLE_REQUESTS,
|
|
18
20
|
handle_request_caching,
|
|
19
21
|
)
|
|
20
22
|
from web3._utils.encoding import (
|
|
@@ -43,23 +45,6 @@ if TYPE_CHECKING:
|
|
|
43
45
|
from web3 import Web3 # noqa: F401
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
CACHEABLE_REQUESTS = cast(
|
|
47
|
-
Set[RPCEndpoint],
|
|
48
|
-
(
|
|
49
|
-
"eth_chainId",
|
|
50
|
-
"eth_getBlockByHash",
|
|
51
|
-
"eth_getBlockTransactionCountByHash",
|
|
52
|
-
"eth_getRawTransactionByHash",
|
|
53
|
-
"eth_getTransactionByBlockHashAndIndex",
|
|
54
|
-
"eth_getTransactionByHash",
|
|
55
|
-
"eth_getUncleByBlockHashAndIndex",
|
|
56
|
-
"eth_getUncleCountByBlockHash",
|
|
57
|
-
"net_version",
|
|
58
|
-
"web3_clientVersion",
|
|
59
|
-
),
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
48
|
class BaseProvider:
|
|
64
49
|
# a tuple of (middleware, request_func)
|
|
65
50
|
_request_func_cache: Tuple[Tuple[Middleware, ...], Callable[..., RPCResponse]] = (
|
|
@@ -73,13 +58,17 @@ class BaseProvider:
|
|
|
73
58
|
ccip_read_max_redirects: int = 4
|
|
74
59
|
|
|
75
60
|
# request caching
|
|
76
|
-
cache_allowed_requests: bool = False
|
|
77
|
-
cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
|
|
78
61
|
_request_cache: SimpleCache
|
|
79
62
|
_request_cache_lock: threading.Lock = threading.Lock()
|
|
80
63
|
|
|
81
|
-
def __init__(
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
cache_allowed_requests: bool = False,
|
|
67
|
+
cacheable_requests: Set[RPCEndpoint] = None,
|
|
68
|
+
) -> None:
|
|
82
69
|
self._request_cache = SimpleCache(1000)
|
|
70
|
+
self.cache_allowed_requests = cache_allowed_requests
|
|
71
|
+
self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
|
|
83
72
|
|
|
84
73
|
def request_func(
|
|
85
74
|
self, w3: "Web3", middleware_onion: MiddlewareOnion
|
|
@@ -115,13 +104,14 @@ class BaseProvider:
|
|
|
115
104
|
|
|
116
105
|
|
|
117
106
|
class JSONBaseProvider(BaseProvider):
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
_is_batching: bool = False
|
|
108
|
+
_batch_request_func_cache: Tuple[
|
|
109
|
+
Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
|
|
110
|
+
] = (None, None)
|
|
121
111
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
113
|
+
self.request_counter = itertools.count()
|
|
114
|
+
super().__init__(**kwargs)
|
|
125
115
|
|
|
126
116
|
def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
|
|
127
117
|
rpc_dict = {
|
|
@@ -133,6 +123,11 @@ class JSONBaseProvider(BaseProvider):
|
|
|
133
123
|
encoded = FriendlyJsonSerde().json_encode(rpc_dict, Web3JsonEncoder)
|
|
134
124
|
return to_bytes(text=encoded)
|
|
135
125
|
|
|
126
|
+
@staticmethod
|
|
127
|
+
def decode_rpc_response(raw_response: bytes) -> RPCResponse:
|
|
128
|
+
text_response = to_text(raw_response)
|
|
129
|
+
return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response))
|
|
130
|
+
|
|
136
131
|
def is_connected(self, show_traceback: bool = False) -> bool:
|
|
137
132
|
try:
|
|
138
133
|
response = self.make_request(RPCEndpoint("web3_clientVersion"), [])
|
|
@@ -156,3 +151,41 @@ class JSONBaseProvider(BaseProvider):
|
|
|
156
151
|
if show_traceback:
|
|
157
152
|
raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
|
|
158
153
|
return False
|
|
154
|
+
|
|
155
|
+
# -- batch requests -- #
|
|
156
|
+
|
|
157
|
+
def batch_request_func(
|
|
158
|
+
self, w3: "Web3", middleware_onion: MiddlewareOnion
|
|
159
|
+
) -> Callable[..., List[RPCResponse]]:
|
|
160
|
+
middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
|
|
161
|
+
|
|
162
|
+
cache_key = self._batch_request_func_cache[0]
|
|
163
|
+
if cache_key != middleware:
|
|
164
|
+
accumulator_fn = self.make_batch_request
|
|
165
|
+
for mw in reversed(middleware):
|
|
166
|
+
initialized = mw(w3)
|
|
167
|
+
# type ignore bc in order to wrap the method, we have to call
|
|
168
|
+
# `wrap_make_batch_request` with the accumulator_fn as the argument
|
|
169
|
+
# which breaks the type hinting for this particular case.
|
|
170
|
+
accumulator_fn = initialized.wrap_make_batch_request(
|
|
171
|
+
accumulator_fn
|
|
172
|
+
) # type: ignore # noqa: E501
|
|
173
|
+
self._batch_request_func_cache = (middleware, accumulator_fn)
|
|
174
|
+
|
|
175
|
+
return self._batch_request_func_cache[-1]
|
|
176
|
+
|
|
177
|
+
def encode_batch_rpc_request(
|
|
178
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
179
|
+
) -> bytes:
|
|
180
|
+
return (
|
|
181
|
+
b"["
|
|
182
|
+
+ b", ".join(
|
|
183
|
+
self.encode_rpc_request(method, params) for method, params in requests
|
|
184
|
+
)
|
|
185
|
+
+ b"]"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def make_batch_request(
|
|
189
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
190
|
+
) -> List[RPCResponse]:
|
|
191
|
+
raise NotImplementedError("Providers must implement this method")
|
web3/providers/ipc.py
CHANGED
|
@@ -14,8 +14,11 @@ from types import (
|
|
|
14
14
|
)
|
|
15
15
|
from typing import (
|
|
16
16
|
Any,
|
|
17
|
+
List,
|
|
18
|
+
Tuple,
|
|
17
19
|
Type,
|
|
18
20
|
Union,
|
|
21
|
+
cast,
|
|
19
22
|
)
|
|
20
23
|
|
|
21
24
|
from web3._utils.threads import (
|
|
@@ -26,6 +29,9 @@ from web3.types import (
|
|
|
26
29
|
RPCResponse,
|
|
27
30
|
)
|
|
28
31
|
|
|
32
|
+
from .._utils.batching import (
|
|
33
|
+
sort_batch_response_by_response_ids,
|
|
34
|
+
)
|
|
29
35
|
from ..exceptions import (
|
|
30
36
|
Web3TypeError,
|
|
31
37
|
Web3ValueError,
|
|
@@ -135,7 +141,6 @@ class IPCProvider(JSONBaseProvider):
|
|
|
135
141
|
self,
|
|
136
142
|
ipc_path: Union[str, Path] = None,
|
|
137
143
|
timeout: int = 30,
|
|
138
|
-
*args: Any,
|
|
139
144
|
**kwargs: Any,
|
|
140
145
|
) -> None:
|
|
141
146
|
if ipc_path is None:
|
|
@@ -148,17 +153,12 @@ class IPCProvider(JSONBaseProvider):
|
|
|
148
153
|
self.timeout = timeout
|
|
149
154
|
self._lock = threading.Lock()
|
|
150
155
|
self._socket = PersistantSocket(self.ipc_path)
|
|
151
|
-
super().__init__()
|
|
156
|
+
super().__init__(**kwargs)
|
|
152
157
|
|
|
153
158
|
def __str__(self) -> str:
|
|
154
159
|
return f"<{self.__class__.__name__} {self.ipc_path}>"
|
|
155
160
|
|
|
156
|
-
def
|
|
157
|
-
self.logger.debug(
|
|
158
|
-
f"Making request IPC. Path: {self.ipc_path}, Method: {method}"
|
|
159
|
-
)
|
|
160
|
-
request = self.encode_rpc_request(method, params)
|
|
161
|
-
|
|
161
|
+
def _make_request(self, request: bytes) -> RPCResponse:
|
|
162
162
|
with self._lock, self._socket as sock:
|
|
163
163
|
try:
|
|
164
164
|
sock.sendall(request)
|
|
@@ -189,6 +189,21 @@ class IPCProvider(JSONBaseProvider):
|
|
|
189
189
|
timeout.sleep(0)
|
|
190
190
|
continue
|
|
191
191
|
|
|
192
|
+
def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
193
|
+
self.logger.debug(
|
|
194
|
+
f"Making request IPC. Path: {self.ipc_path}, Method: {method}"
|
|
195
|
+
)
|
|
196
|
+
request = self.encode_rpc_request(method, params)
|
|
197
|
+
return self._make_request(request)
|
|
198
|
+
|
|
199
|
+
def make_batch_request(
|
|
200
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
201
|
+
) -> List[RPCResponse]:
|
|
202
|
+
self.logger.debug(f"Making batch request IPC. Path: {self.ipc_path}")
|
|
203
|
+
request_data = self.encode_batch_rpc_request(requests)
|
|
204
|
+
response = cast(List[RPCResponse], self._make_request(request_data))
|
|
205
|
+
return sort_batch_response_by_response_ids(response)
|
|
206
|
+
|
|
192
207
|
|
|
193
208
|
# A valid JSON RPC response can only end in } or ] http://www.jsonrpc.org/specification
|
|
194
209
|
def has_valid_json_rpc_ending(raw_response: bytes) -> bool:
|
|
@@ -10,9 +10,12 @@ from types import (
|
|
|
10
10
|
)
|
|
11
11
|
from typing import (
|
|
12
12
|
Any,
|
|
13
|
+
List,
|
|
13
14
|
Optional,
|
|
15
|
+
Tuple,
|
|
14
16
|
Type,
|
|
15
17
|
Union,
|
|
18
|
+
cast,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
21
|
from eth_typing import (
|
|
@@ -25,6 +28,12 @@ from websockets.legacy.client import (
|
|
|
25
28
|
WebSocketClientProtocol,
|
|
26
29
|
)
|
|
27
30
|
|
|
31
|
+
from web3._utils.batching import (
|
|
32
|
+
sort_batch_response_by_response_ids,
|
|
33
|
+
)
|
|
34
|
+
from web3._utils.caching import (
|
|
35
|
+
handle_request_caching,
|
|
36
|
+
)
|
|
28
37
|
from web3.exceptions import (
|
|
29
38
|
Web3ValidationError,
|
|
30
39
|
)
|
|
@@ -91,6 +100,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
|
|
|
91
100
|
endpoint_uri: Optional[Union[URI, str]] = None,
|
|
92
101
|
websocket_kwargs: Optional[Any] = None,
|
|
93
102
|
websocket_timeout: int = DEFAULT_WEBSOCKET_TIMEOUT,
|
|
103
|
+
**kwargs: Any,
|
|
94
104
|
) -> None:
|
|
95
105
|
self.endpoint_uri = URI(endpoint_uri)
|
|
96
106
|
self.websocket_timeout = websocket_timeout
|
|
@@ -110,7 +120,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
|
|
|
110
120
|
f"in websocket_kwargs, found: {found_restricted_keys}"
|
|
111
121
|
)
|
|
112
122
|
self.conn = PersistentWebSocket(self.endpoint_uri, websocket_kwargs)
|
|
113
|
-
super().__init__()
|
|
123
|
+
super().__init__(**kwargs)
|
|
114
124
|
|
|
115
125
|
def __str__(self) -> str:
|
|
116
126
|
return f"WS connection {self.endpoint_uri}"
|
|
@@ -124,6 +134,7 @@ class LegacyWebSocketProvider(JSONBaseProvider):
|
|
|
124
134
|
await asyncio.wait_for(conn.recv(), timeout=self.websocket_timeout)
|
|
125
135
|
)
|
|
126
136
|
|
|
137
|
+
@handle_request_caching
|
|
127
138
|
def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
128
139
|
self.logger.debug(
|
|
129
140
|
f"Making request WebSocket. URI: {self.endpoint_uri}, " f"Method: {method}"
|
|
@@ -133,3 +144,17 @@ class LegacyWebSocketProvider(JSONBaseProvider):
|
|
|
133
144
|
self.coro_make_request(request_data), LegacyWebSocketProvider._loop
|
|
134
145
|
)
|
|
135
146
|
return future.result()
|
|
147
|
+
|
|
148
|
+
def make_batch_request(
|
|
149
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
150
|
+
) -> List[RPCResponse]:
|
|
151
|
+
self.logger.debug(
|
|
152
|
+
f"Making batch request WebSocket. URI: {self.endpoint_uri}, "
|
|
153
|
+
f"Methods: {requests}"
|
|
154
|
+
)
|
|
155
|
+
request_data = self.encode_batch_rpc_request(requests)
|
|
156
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
157
|
+
self.coro_make_request(request_data), LegacyWebSocketProvider._loop
|
|
158
|
+
)
|
|
159
|
+
response = cast(List[RPCResponse], future.result())
|
|
160
|
+
return sort_batch_response_by_response_ids(response)
|
|
@@ -11,9 +11,11 @@ from pathlib import (
|
|
|
11
11
|
import sys
|
|
12
12
|
from typing import (
|
|
13
13
|
Any,
|
|
14
|
+
List,
|
|
14
15
|
Optional,
|
|
15
16
|
Tuple,
|
|
16
17
|
Union,
|
|
18
|
+
cast,
|
|
17
19
|
)
|
|
18
20
|
|
|
19
21
|
from eth_utils import (
|
|
@@ -28,6 +30,10 @@ from web3.types import (
|
|
|
28
30
|
from . import (
|
|
29
31
|
PersistentConnectionProvider,
|
|
30
32
|
)
|
|
33
|
+
from ..._utils.batching import (
|
|
34
|
+
BATCH_REQUEST_ID,
|
|
35
|
+
sort_batch_response_by_response_ids,
|
|
36
|
+
)
|
|
31
37
|
from ..._utils.caching import (
|
|
32
38
|
async_handle_request_caching,
|
|
33
39
|
)
|
|
@@ -59,11 +65,12 @@ class AsyncIPCProvider(PersistentConnectionProvider):
|
|
|
59
65
|
|
|
60
66
|
_reader: Optional[asyncio.StreamReader] = None
|
|
61
67
|
_writer: Optional[asyncio.StreamWriter] = None
|
|
68
|
+
_decoder: json.JSONDecoder = json.JSONDecoder()
|
|
69
|
+
_raw_message: str = ""
|
|
62
70
|
|
|
63
71
|
def __init__(
|
|
64
72
|
self,
|
|
65
73
|
ipc_path: Optional[Union[str, Path]] = None,
|
|
66
|
-
max_connection_retries: int = 5,
|
|
67
74
|
# `PersistentConnectionProvider` kwargs can be passed through
|
|
68
75
|
**kwargs: Any,
|
|
69
76
|
) -> None:
|
|
@@ -74,7 +81,6 @@ class AsyncIPCProvider(PersistentConnectionProvider):
|
|
|
74
81
|
else:
|
|
75
82
|
raise Web3TypeError("ipc_path must be of type string or pathlib.Path")
|
|
76
83
|
|
|
77
|
-
self._max_connection_retries = max_connection_retries
|
|
78
84
|
super().__init__(**kwargs)
|
|
79
85
|
|
|
80
86
|
def __str__(self) -> str:
|
|
@@ -99,48 +105,16 @@ class AsyncIPCProvider(PersistentConnectionProvider):
|
|
|
99
105
|
)
|
|
100
106
|
return False
|
|
101
107
|
|
|
102
|
-
async def
|
|
103
|
-
|
|
104
|
-
_backoff_rate_change = 1.75
|
|
105
|
-
_backoff_time = 1.75
|
|
106
|
-
|
|
107
|
-
while _connection_attempts != self._max_connection_retries:
|
|
108
|
-
try:
|
|
109
|
-
_connection_attempts += 1
|
|
110
|
-
self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
|
|
111
|
-
self._message_listener_task = asyncio.create_task(
|
|
112
|
-
self._message_listener()
|
|
113
|
-
)
|
|
114
|
-
break
|
|
115
|
-
except OSError as e:
|
|
116
|
-
if _connection_attempts == self._max_connection_retries:
|
|
117
|
-
raise ProviderConnectionError(
|
|
118
|
-
f"Could not connect to: {self.ipc_path}. "
|
|
119
|
-
f"Retries exceeded max of {self._max_connection_retries}."
|
|
120
|
-
) from e
|
|
121
|
-
self.logger.info(
|
|
122
|
-
f"Could not connect to: {self.ipc_path}. Retrying in "
|
|
123
|
-
f"{round(_backoff_time, 1)} seconds.",
|
|
124
|
-
exc_info=True,
|
|
125
|
-
)
|
|
126
|
-
await asyncio.sleep(_backoff_time)
|
|
127
|
-
_backoff_time *= _backoff_rate_change
|
|
108
|
+
async def _provider_specific_connect(self) -> None:
|
|
109
|
+
self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
|
|
128
110
|
|
|
129
|
-
async def
|
|
111
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
130
112
|
if self._writer and not self._writer.is_closing():
|
|
131
113
|
self._writer.close()
|
|
132
114
|
await self._writer.wait_closed()
|
|
133
115
|
self._writer = None
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
self._message_listener_task.cancel()
|
|
138
|
-
await self._message_listener_task
|
|
116
|
+
if self._reader:
|
|
139
117
|
self._reader = None
|
|
140
|
-
except (asyncio.CancelledError, StopAsyncIteration):
|
|
141
|
-
pass
|
|
142
|
-
|
|
143
|
-
self._request_processor.clear_caches()
|
|
144
118
|
|
|
145
119
|
async def _reset_socket(self) -> None:
|
|
146
120
|
self._writer.close()
|
|
@@ -149,13 +123,12 @@ class AsyncIPCProvider(PersistentConnectionProvider):
|
|
|
149
123
|
|
|
150
124
|
@async_handle_request_caching
|
|
151
125
|
async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
152
|
-
request_data = self.encode_rpc_request(method, params)
|
|
153
|
-
|
|
154
126
|
if self._writer is None:
|
|
155
127
|
raise ProviderConnectionError(
|
|
156
128
|
"Connection to ipc socket has not been initiated for the provider."
|
|
157
129
|
)
|
|
158
130
|
|
|
131
|
+
request_data = self.encode_rpc_request(method, params)
|
|
159
132
|
try:
|
|
160
133
|
self._writer.write(request_data)
|
|
161
134
|
await self._writer.drain()
|
|
@@ -172,43 +145,54 @@ class AsyncIPCProvider(PersistentConnectionProvider):
|
|
|
172
145
|
|
|
173
146
|
return response
|
|
174
147
|
|
|
175
|
-
async def
|
|
176
|
-
self
|
|
177
|
-
|
|
178
|
-
|
|
148
|
+
async def make_batch_request(
|
|
149
|
+
self, requests: List[Tuple[RPCEndpoint, Any]]
|
|
150
|
+
) -> List[RPCResponse]:
|
|
151
|
+
if self._writer is None:
|
|
152
|
+
raise ProviderConnectionError(
|
|
153
|
+
"Connection to ipc socket has not been initiated for the provider."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
request_data = self.encode_batch_rpc_request(requests)
|
|
157
|
+
try:
|
|
158
|
+
self._writer.write(request_data)
|
|
159
|
+
await self._writer.drain()
|
|
160
|
+
except OSError as e:
|
|
161
|
+
# Broken pipe
|
|
162
|
+
if e.errno == errno.EPIPE:
|
|
163
|
+
# one extra attempt, then give up
|
|
164
|
+
await self._reset_socket()
|
|
165
|
+
self._writer.write(request_data)
|
|
166
|
+
await self._writer.drain()
|
|
167
|
+
|
|
168
|
+
response = cast(
|
|
169
|
+
List[RPCResponse], await self._get_response_for_request_id(BATCH_REQUEST_ID)
|
|
179
170
|
)
|
|
180
|
-
|
|
181
|
-
decoder = json.JSONDecoder()
|
|
171
|
+
return response
|
|
182
172
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
# back to the event loop to share the loop with other tasks.
|
|
186
|
-
await asyncio.sleep(0)
|
|
173
|
+
async def _provider_specific_message_listener(self) -> None:
|
|
174
|
+
self._raw_message += to_text(await self._reader.read(4096)).lstrip()
|
|
187
175
|
|
|
176
|
+
while self._raw_message:
|
|
188
177
|
try:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"Exception caught in listener, error logging and keeping listener "
|
|
211
|
-
f"background task alive.\n error={e}"
|
|
212
|
-
)
|
|
213
|
-
# if only error logging, reset the ``raw_message`` buffer and continue
|
|
214
|
-
raw_message = ""
|
|
178
|
+
response, pos = self._decoder.raw_decode(self._raw_message)
|
|
179
|
+
except JSONDecodeError:
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
if isinstance(response, list):
|
|
183
|
+
response = sort_batch_response_by_response_ids(response)
|
|
184
|
+
|
|
185
|
+
is_subscription = (
|
|
186
|
+
response.get("method") == "eth_subscription"
|
|
187
|
+
if not isinstance(response, list)
|
|
188
|
+
else False
|
|
189
|
+
)
|
|
190
|
+
await self._request_processor.cache_raw_response(
|
|
191
|
+
response, subscription=is_subscription
|
|
192
|
+
)
|
|
193
|
+
self._raw_message = self._raw_message[pos:].lstrip()
|
|
194
|
+
|
|
195
|
+
def _error_log_listener_task_exception(self, e: Exception) -> None:
|
|
196
|
+
super()._error_log_listener_task_exception(e)
|
|
197
|
+
# reset the raw message buffer on exception when error logging
|
|
198
|
+
self._raw_message = ""
|