web3 6.20.3__py3-none-any.whl → 7.0.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.
Files changed (270) hide show
  1. ens/__init__.py +13 -2
  2. ens/_normalization.py +2 -17
  3. ens/async_ens.py +33 -21
  4. ens/base_ens.py +3 -1
  5. ens/ens.py +16 -11
  6. ens/exceptions.py +16 -29
  7. ens/specs/nf.json +1 -1
  8. ens/specs/normalization_spec.json +1 -1
  9. ens/utils.py +52 -63
  10. web3/__init__.py +20 -24
  11. web3/_utils/abi.py +115 -271
  12. web3/_utils/async_transactions.py +7 -4
  13. web3/_utils/batching.py +217 -0
  14. web3/_utils/blocks.py +6 -2
  15. web3/_utils/caching.py +128 -5
  16. web3/_utils/compat/__init__.py +2 -3
  17. web3/_utils/contract_sources/compile_contracts.py +1 -1
  18. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  19. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  20. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  21. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  22. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/event_contracts.py +5 -5
  24. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  25. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  26. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  27. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  28. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  29. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  30. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  31. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  32. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  33. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  34. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  35. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  36. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  37. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  38. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  39. web3/_utils/contracts.py +130 -236
  40. web3/_utils/datatypes.py +5 -1
  41. web3/_utils/decorators.py +13 -23
  42. web3/_utils/empty.py +1 -1
  43. web3/_utils/encoding.py +16 -12
  44. web3/_utils/ens.py +2 -1
  45. web3/_utils/error_formatters_utils.py +5 -3
  46. web3/_utils/events.py +66 -69
  47. web3/_utils/fee_utils.py +1 -3
  48. web3/_utils/filters.py +24 -22
  49. web3/_utils/formatters.py +2 -2
  50. web3/_utils/http.py +5 -3
  51. web3/_utils/http_session_manager.py +303 -0
  52. web3/_utils/math.py +14 -15
  53. web3/_utils/method_formatters.py +34 -36
  54. web3/_utils/module.py +2 -1
  55. web3/_utils/module_testing/__init__.py +0 -3
  56. web3/_utils/module_testing/eth_module.py +695 -643
  57. web3/_utils/module_testing/module_testing_utils.py +61 -34
  58. web3/_utils/module_testing/persistent_connection_provider.py +56 -25
  59. web3/_utils/module_testing/utils.py +258 -0
  60. web3/_utils/module_testing/web3_module.py +438 -17
  61. web3/_utils/normalizers.py +13 -11
  62. web3/_utils/rpc_abi.py +5 -32
  63. web3/_utils/threads.py +8 -7
  64. web3/_utils/transactions.py +14 -12
  65. web3/_utils/type_conversion.py +5 -1
  66. web3/_utils/validation.py +17 -17
  67. web3/auto/gethdev.py +7 -2
  68. web3/beacon/__init__.py +6 -1
  69. web3/beacon/async_beacon.py +9 -5
  70. web3/beacon/{main.py → beacon.py} +7 -5
  71. web3/contract/__init__.py +7 -0
  72. web3/contract/async_contract.py +47 -46
  73. web3/contract/base_contract.py +183 -158
  74. web3/contract/contract.py +49 -43
  75. web3/contract/utils.py +203 -59
  76. web3/datastructures.py +79 -31
  77. web3/eth/__init__.py +7 -0
  78. web3/eth/async_eth.py +39 -51
  79. web3/eth/base_eth.py +17 -10
  80. web3/eth/eth.py +30 -68
  81. web3/exceptions.py +108 -82
  82. web3/gas_strategies/time_based.py +8 -6
  83. web3/geth.py +1 -254
  84. web3/main.py +75 -122
  85. web3/manager.py +316 -146
  86. web3/method.py +38 -31
  87. web3/middleware/__init__.py +67 -89
  88. web3/middleware/attrdict.py +36 -49
  89. web3/middleware/base.py +174 -0
  90. web3/middleware/buffered_gas_estimate.py +20 -21
  91. web3/middleware/filter.py +157 -117
  92. web3/middleware/formatting.py +124 -108
  93. web3/middleware/gas_price_strategy.py +20 -32
  94. web3/middleware/names.py +29 -26
  95. web3/middleware/proof_of_authority.py +68 -0
  96. web3/middleware/pythonic.py +2 -2
  97. web3/middleware/signing.py +74 -89
  98. web3/middleware/stalecheck.py +52 -79
  99. web3/middleware/validation.py +5 -13
  100. web3/module.py +54 -10
  101. web3/providers/__init__.py +10 -6
  102. web3/providers/async_base.py +117 -39
  103. web3/providers/auto.py +3 -3
  104. web3/providers/base.py +89 -33
  105. web3/providers/eth_tester/__init__.py +5 -0
  106. web3/providers/eth_tester/defaults.py +1 -64
  107. web3/providers/eth_tester/main.py +99 -31
  108. web3/providers/eth_tester/middleware.py +45 -73
  109. web3/providers/ipc.py +42 -46
  110. web3/providers/{websocket/websocket.py → legacy_websocket.py} +32 -7
  111. web3/providers/persistent/__init__.py +22 -0
  112. web3/providers/persistent/async_ipc.py +153 -0
  113. web3/providers/{persistent.py → persistent/persistent.py} +106 -25
  114. web3/providers/persistent/persistent_connection.py +84 -0
  115. web3/providers/{websocket → persistent}/request_processor.py +94 -32
  116. web3/providers/persistent/utils.py +43 -0
  117. web3/providers/{websocket/websocket_v2.py → persistent/websocket.py} +29 -28
  118. web3/providers/rpc/__init__.py +11 -0
  119. web3/providers/rpc/async_rpc.py +171 -0
  120. web3/providers/rpc/rpc.py +179 -0
  121. web3/providers/rpc/utils.py +92 -0
  122. web3/testing.py +4 -4
  123. web3/tools/benchmark/main.py +22 -22
  124. web3/tools/benchmark/node.py +2 -8
  125. web3/tools/benchmark/reporting.py +2 -2
  126. web3/tools/benchmark/utils.py +1 -1
  127. web3/tracing.py +9 -5
  128. web3/types.py +30 -107
  129. web3/utils/__init__.py +58 -5
  130. web3/utils/abi.py +575 -10
  131. web3/utils/async_exception_handling.py +19 -7
  132. web3/utils/caching.py +32 -13
  133. web3/utils/exception_handling.py +7 -5
  134. {web3-6.20.3.dist-info → web3-7.0.0.dist-info}/LICENSE +1 -1
  135. web3-7.0.0.dist-info/METADATA +112 -0
  136. web3-7.0.0.dist-info/RECORD +167 -0
  137. {web3-6.20.3.dist-info → web3-7.0.0.dist-info}/top_level.txt +0 -1
  138. ethpm/__init__.py +0 -20
  139. ethpm/_utils/__init__.py +0 -0
  140. ethpm/_utils/backend.py +0 -93
  141. ethpm/_utils/cache.py +0 -44
  142. ethpm/_utils/chains.py +0 -119
  143. ethpm/_utils/contract.py +0 -35
  144. ethpm/_utils/deployments.py +0 -145
  145. ethpm/_utils/ipfs.py +0 -116
  146. ethpm/_utils/protobuf/__init__.py +0 -0
  147. ethpm/_utils/protobuf/ipfs_file_pb2.py +0 -33
  148. ethpm/_utils/registry.py +0 -29
  149. ethpm/assets/__init__.py +0 -0
  150. ethpm/assets/ens/v3.json +0 -1
  151. ethpm/assets/escrow/with_bytecode_v3.json +0 -1
  152. ethpm/assets/ipfs_file.proto +0 -32
  153. ethpm/assets/owned/output_v3.json +0 -1
  154. ethpm/assets/owned/with_contract_type_v3.json +0 -1
  155. ethpm/assets/registry/contracts/Authority.sol +0 -156
  156. ethpm/assets/registry/contracts/IndexedOrderedSetLib.sol +0 -106
  157. ethpm/assets/registry/contracts/PackageDB.sol +0 -225
  158. ethpm/assets/registry/contracts/PackageRegistry.sol +0 -361
  159. ethpm/assets/registry/contracts/PackageRegistryInterface.sol +0 -97
  160. ethpm/assets/registry/contracts/ReleaseDB.sol +0 -309
  161. ethpm/assets/registry/contracts/ReleaseValidator.sol +0 -152
  162. ethpm/assets/registry/solc_input.json +0 -1
  163. ethpm/assets/registry/solc_output.json +0 -1
  164. ethpm/assets/registry/v3.json +0 -1
  165. ethpm/assets/safe-math-lib/v3-strict-no-deployments.json +0 -1
  166. ethpm/assets/simple-registry/contracts/Ownable.sol +0 -63
  167. ethpm/assets/simple-registry/contracts/PackageRegistry.sol +0 -373
  168. ethpm/assets/simple-registry/contracts/PackageRegistryInterface.sol +0 -96
  169. ethpm/assets/simple-registry/solc_input.json +0 -33
  170. ethpm/assets/simple-registry/solc_output.json +0 -1
  171. ethpm/assets/simple-registry/v3.json +0 -1
  172. ethpm/assets/standard-token/output_v3.json +0 -1
  173. ethpm/assets/standard-token/with_bytecode_v3.json +0 -1
  174. ethpm/assets/vyper_registry/0.1.0.json +0 -1
  175. ethpm/assets/vyper_registry/registry.vy +0 -216
  176. ethpm/assets/vyper_registry/registry_with_delete.vy +0 -244
  177. ethpm/backends/__init__.py +0 -0
  178. ethpm/backends/base.py +0 -43
  179. ethpm/backends/http.py +0 -108
  180. ethpm/backends/ipfs.py +0 -219
  181. ethpm/backends/registry.py +0 -154
  182. ethpm/constants.py +0 -17
  183. ethpm/contract.py +0 -187
  184. ethpm/dependencies.py +0 -58
  185. ethpm/deployments.py +0 -80
  186. ethpm/ethpm-spec/examples/escrow/1.0.0-pretty.json +0 -146
  187. ethpm/ethpm-spec/examples/escrow/1.0.0.json +0 -1
  188. ethpm/ethpm-spec/examples/escrow/contracts/Escrow.sol +0 -32
  189. ethpm/ethpm-spec/examples/escrow/contracts/SafeSendLib.sol +0 -20
  190. ethpm/ethpm-spec/examples/escrow/v3-pretty.json +0 -171
  191. ethpm/ethpm-spec/examples/escrow/v3.json +0 -1
  192. ethpm/ethpm-spec/examples/owned/1.0.0-pretty.json +0 -21
  193. ethpm/ethpm-spec/examples/owned/1.0.0.json +0 -1
  194. ethpm/ethpm-spec/examples/owned/contracts/Owned.sol +0 -12
  195. ethpm/ethpm-spec/examples/owned/v3-pretty.json +0 -27
  196. ethpm/ethpm-spec/examples/owned/v3.json +0 -1
  197. ethpm/ethpm-spec/examples/piper-coin/1.0.0-pretty.json +0 -31
  198. ethpm/ethpm-spec/examples/piper-coin/1.0.0.json +0 -1
  199. ethpm/ethpm-spec/examples/piper-coin/v3-pretty.json +0 -21
  200. ethpm/ethpm-spec/examples/piper-coin/v3.json +0 -1
  201. ethpm/ethpm-spec/examples/safe-math-lib/1.0.0-pretty.json +0 -85
  202. ethpm/ethpm-spec/examples/safe-math-lib/1.0.0.json +0 -1
  203. ethpm/ethpm-spec/examples/safe-math-lib/contracts/SafeMathLib.sol +0 -24
  204. ethpm/ethpm-spec/examples/safe-math-lib/v3-pretty.json +0 -117
  205. ethpm/ethpm-spec/examples/safe-math-lib/v3.json +0 -1
  206. ethpm/ethpm-spec/examples/standard-token/1.0.0-pretty.json +0 -55
  207. ethpm/ethpm-spec/examples/standard-token/1.0.0.json +0 -1
  208. ethpm/ethpm-spec/examples/standard-token/contracts/AbstractToken.sol +0 -20
  209. ethpm/ethpm-spec/examples/standard-token/contracts/StandardToken.sol +0 -84
  210. ethpm/ethpm-spec/examples/standard-token/v3-pretty.json +0 -460
  211. ethpm/ethpm-spec/examples/standard-token/v3.json +0 -1
  212. ethpm/ethpm-spec/examples/transferable/1.0.0-pretty.json +0 -21
  213. ethpm/ethpm-spec/examples/transferable/1.0.0.json +0 -1
  214. ethpm/ethpm-spec/examples/transferable/contracts/Transferable.sol +0 -14
  215. ethpm/ethpm-spec/examples/transferable/v3-pretty.json +0 -27
  216. ethpm/ethpm-spec/examples/transferable/v3.json +0 -1
  217. ethpm/ethpm-spec/examples/wallet/1.0.0-pretty.json +0 -120
  218. ethpm/ethpm-spec/examples/wallet/1.0.0.json +0 -1
  219. ethpm/ethpm-spec/examples/wallet/contracts/Wallet.sol +0 -41
  220. ethpm/ethpm-spec/examples/wallet/v3-pretty.json +0 -181
  221. ethpm/ethpm-spec/examples/wallet/v3.json +0 -1
  222. ethpm/ethpm-spec/examples/wallet-with-send/1.0.0-pretty.json +0 -135
  223. ethpm/ethpm-spec/examples/wallet-with-send/1.0.0.json +0 -1
  224. ethpm/ethpm-spec/examples/wallet-with-send/contracts/WalletWithSend.sol +0 -18
  225. ethpm/ethpm-spec/examples/wallet-with-send/v3-pretty.json +0 -207
  226. ethpm/ethpm-spec/examples/wallet-with-send/v3.json +0 -1
  227. ethpm/ethpm-spec/spec/package.spec.json +0 -379
  228. ethpm/ethpm-spec/spec/v3.spec.json +0 -483
  229. ethpm/exceptions.py +0 -68
  230. ethpm/package.py +0 -438
  231. ethpm/tools/__init__.py +0 -4
  232. ethpm/tools/builder.py +0 -930
  233. ethpm/tools/checker.py +0 -312
  234. ethpm/tools/get_manifest.py +0 -19
  235. ethpm/uri.py +0 -141
  236. ethpm/validation/__init__.py +0 -0
  237. ethpm/validation/manifest.py +0 -146
  238. ethpm/validation/misc.py +0 -39
  239. ethpm/validation/package.py +0 -80
  240. ethpm/validation/uri.py +0 -163
  241. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  242. web3/_utils/miner.py +0 -88
  243. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -323
  244. web3/_utils/request.py +0 -265
  245. web3/middleware/abi.py +0 -11
  246. web3/middleware/async_cache.py +0 -99
  247. web3/middleware/cache.py +0 -374
  248. web3/middleware/exception_handling.py +0 -49
  249. web3/middleware/exception_retry_request.py +0 -188
  250. web3/middleware/fixture.py +0 -190
  251. web3/middleware/geth_poa.py +0 -81
  252. web3/middleware/normalize_request_parameters.py +0 -11
  253. web3/middleware/simulate_unmined_transaction.py +0 -43
  254. web3/pm.py +0 -602
  255. web3/providers/async_rpc.py +0 -99
  256. web3/providers/rpc.py +0 -98
  257. web3/providers/websocket/__init__.py +0 -11
  258. web3/providers/websocket/websocket_connection.py +0 -42
  259. web3/tools/__init__.py +0 -4
  260. web3/tools/pytest_ethereum/__init__.py +0 -0
  261. web3/tools/pytest_ethereum/_utils.py +0 -145
  262. web3/tools/pytest_ethereum/deployer.py +0 -48
  263. web3/tools/pytest_ethereum/exceptions.py +0 -22
  264. web3/tools/pytest_ethereum/linker.py +0 -128
  265. web3/tools/pytest_ethereum/plugins.py +0 -33
  266. web3-6.20.3.dist-info/METADATA +0 -104
  267. web3-6.20.3.dist-info/RECORD +0 -283
  268. web3-6.20.3.dist-info/entry_points.txt +0 -2
  269. /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
  270. {web3-6.20.3.dist-info → web3-7.0.0.dist-info}/WHEEL +0 -0
@@ -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
  )
@@ -37,7 +46,7 @@ from web3.types import (
37
46
  )
38
47
 
39
48
  RESTRICTED_WEBSOCKET_KWARGS = {"uri", "loop"}
40
- DEFAULT_WEBSOCKET_TIMEOUT = 10
49
+ DEFAULT_WEBSOCKET_TIMEOUT = 30
41
50
 
42
51
 
43
52
  def _start_event_loop(loop: asyncio.AbstractEventLoop) -> None:
@@ -82,8 +91,8 @@ class PersistentWebSocket:
82
91
  self.ws = None
83
92
 
84
93
 
85
- class WebsocketProvider(JSONBaseProvider):
86
- logger = logging.getLogger("web3.providers.WebsocketProvider")
94
+ class LegacyWebSocketProvider(JSONBaseProvider):
95
+ logger = logging.getLogger("web3.providers.WebSocketProvider")
87
96
  _loop = None
88
97
 
89
98
  def __init__(
@@ -91,13 +100,14 @@ class WebsocketProvider(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
97
107
  if self.endpoint_uri is None:
98
108
  self.endpoint_uri = get_default_endpoint()
99
- if WebsocketProvider._loop is None:
100
- WebsocketProvider._loop = _get_threaded_loop()
109
+ if LegacyWebSocketProvider._loop is None:
110
+ LegacyWebSocketProvider._loop = _get_threaded_loop()
101
111
  if websocket_kwargs is None:
102
112
  websocket_kwargs = {}
103
113
  else:
@@ -110,7 +120,7 @@ class WebsocketProvider(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,12 +134,27 @@ class WebsocketProvider(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}"
130
141
  )
131
142
  request_data = self.encode_rpc_request(method, params)
132
143
  future = asyncio.run_coroutine_threadsafe(
133
- self.coro_make_request(request_data), WebsocketProvider._loop
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)
@@ -0,0 +1,22 @@
1
+ from .persistent import (
2
+ PersistentConnectionProvider,
3
+ )
4
+ from .persistent_connection import (
5
+ PersistentConnection,
6
+ )
7
+ from .request_processor import (
8
+ RequestProcessor,
9
+ )
10
+ from .async_ipc import (
11
+ AsyncIPCProvider,
12
+ )
13
+ from .websocket import (
14
+ WebSocketProvider,
15
+ )
16
+
17
+ __all__ = [
18
+ "PersistentConnectionProvider",
19
+ "PersistentConnection",
20
+ "AsyncIPCProvider",
21
+ "WebSocketProvider",
22
+ ]
@@ -0,0 +1,153 @@
1
+ import asyncio
2
+ import errno
3
+ import json
4
+ from json import (
5
+ JSONDecodeError,
6
+ )
7
+ import logging
8
+ from pathlib import (
9
+ Path,
10
+ )
11
+ import sys
12
+ from typing import (
13
+ Any,
14
+ Optional,
15
+ Tuple,
16
+ Union,
17
+ )
18
+
19
+ from eth_utils import (
20
+ to_text,
21
+ )
22
+
23
+ from web3.types import (
24
+ RPCResponse,
25
+ )
26
+
27
+ from . import (
28
+ PersistentConnectionProvider,
29
+ )
30
+ from ...exceptions import (
31
+ ProviderConnectionError,
32
+ Web3TypeError,
33
+ )
34
+ from ..ipc import (
35
+ get_default_ipc_path,
36
+ )
37
+
38
+
39
+ async def async_get_ipc_socket(
40
+ ipc_path: str,
41
+ ) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]:
42
+ if sys.platform == "win32":
43
+ # On Windows named pipe is used. Simulate socket with it.
44
+ from web3._utils.windows import (
45
+ NamedPipe,
46
+ )
47
+
48
+ return NamedPipe(ipc_path)
49
+ else:
50
+ return await asyncio.open_unix_connection(ipc_path)
51
+
52
+
53
+ class AsyncIPCProvider(PersistentConnectionProvider):
54
+ logger = logging.getLogger("web3.providers.AsyncIPCProvider")
55
+
56
+ _reader: Optional[asyncio.StreamReader] = None
57
+ _writer: Optional[asyncio.StreamWriter] = None
58
+ _decoder: json.JSONDecoder = json.JSONDecoder()
59
+ _raw_message: str = ""
60
+
61
+ def __init__(
62
+ self,
63
+ ipc_path: Optional[Union[str, Path]] = None,
64
+ # `PersistentConnectionProvider` kwargs can be passed through
65
+ **kwargs: Any,
66
+ ) -> None:
67
+ if ipc_path is None:
68
+ self.ipc_path = get_default_ipc_path()
69
+ elif isinstance(ipc_path, str) or isinstance(ipc_path, Path):
70
+ self.ipc_path = str(Path(ipc_path).expanduser().resolve())
71
+ else:
72
+ raise Web3TypeError("ipc_path must be of type string or pathlib.Path")
73
+
74
+ super().__init__(**kwargs)
75
+
76
+ def __str__(self) -> str:
77
+ return f"<{self.__class__.__name__} {self.ipc_path}>"
78
+
79
+ async def is_connected(self, show_traceback: bool = False) -> bool:
80
+ if not self._writer or not self._reader:
81
+ return False
82
+
83
+ try:
84
+ await self.make_request("web3_clientVersion", [])
85
+ return True
86
+ except (OSError, ProviderConnectionError) as e:
87
+ if show_traceback:
88
+ raise ProviderConnectionError(
89
+ f"Problem connecting to provider with error: {type(e)}: {e}"
90
+ )
91
+ return False
92
+
93
+ async def socket_send(self, request_data: bytes) -> None:
94
+ if self._writer is None:
95
+ raise ProviderConnectionError(
96
+ "Connection to ipc socket has not been initiated for the provider."
97
+ )
98
+
99
+ return await asyncio.wait_for(
100
+ self._socket_send(request_data), timeout=self.request_timeout
101
+ )
102
+
103
+ async def socket_recv(self) -> RPCResponse:
104
+ while True:
105
+ # yield to the event loop to allow other tasks to run
106
+ await asyncio.sleep(0)
107
+
108
+ try:
109
+ response, pos = self._decoder.raw_decode(self._raw_message)
110
+ self._raw_message = self._raw_message[pos:].lstrip()
111
+ return response
112
+ except JSONDecodeError:
113
+ # read more data from the socket if the current raw message is
114
+ # incomplete
115
+ self._raw_message += to_text(await self._reader.read(4096)).lstrip()
116
+
117
+ # -- private methods -- #
118
+
119
+ async def _socket_send(self, request_data: bytes) -> None:
120
+ try:
121
+ self._writer.write(request_data)
122
+ await self._writer.drain()
123
+ except OSError as e:
124
+ # Broken pipe
125
+ if e.errno == errno.EPIPE:
126
+ # one extra attempt, then give up
127
+ await self._reset_socket()
128
+ self._writer.write(request_data)
129
+ await self._writer.drain()
130
+
131
+ async def _reset_socket(self) -> None:
132
+ self._writer.close()
133
+ await self._writer.wait_closed()
134
+ self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
135
+
136
+ async def _provider_specific_connect(self) -> None:
137
+ self._reader, self._writer = await async_get_ipc_socket(self.ipc_path)
138
+
139
+ async def _provider_specific_disconnect(self) -> None:
140
+ if self._writer and not self._writer.is_closing():
141
+ self._writer.close()
142
+ await self._writer.wait_closed()
143
+ self._writer = None
144
+ if self._reader:
145
+ self._reader = None
146
+
147
+ async def _provider_specific_socket_reader(self) -> RPCResponse:
148
+ return await self.socket_recv()
149
+
150
+ def _error_log_listener_task_exception(self, e: Exception) -> None:
151
+ super()._error_log_listener_task_exception(e)
152
+ # reset the raw message buffer on exception when error logging
153
+ self._raw_message = ""
@@ -1,68 +1,91 @@
1
1
  from abc import (
2
2
  ABC,
3
+ abstractmethod,
3
4
  )
4
5
  import asyncio
6
+ import json
5
7
  import logging
6
8
  from typing import (
9
+ Any,
10
+ List,
7
11
  Optional,
12
+ Tuple,
13
+ Union,
14
+ cast,
8
15
  )
9
16
 
10
17
  from websockets import (
11
18
  ConnectionClosed,
12
- ConnectionClosedOK,
13
- WebSocketClientProtocol,
14
19
  WebSocketException,
15
20
  )
16
21
 
22
+ from web3._utils.batching import (
23
+ BATCH_REQUEST_ID,
24
+ sort_batch_response_by_response_ids,
25
+ )
17
26
  from web3._utils.caching import (
27
+ async_handle_request_caching,
18
28
  generate_cache_key,
19
29
  )
20
30
  from web3.exceptions import (
31
+ PersistentConnectionClosedOK,
21
32
  ProviderConnectionError,
22
33
  TaskNotRunning,
23
34
  TimeExhausted,
35
+ Web3AttributeError,
24
36
  )
25
37
  from web3.providers.async_base import (
26
38
  AsyncJSONBaseProvider,
27
39
  )
28
- from web3.providers.websocket.request_processor import (
40
+ from web3.providers.persistent.request_processor import (
29
41
  RequestProcessor,
30
42
  )
31
43
  from web3.types import (
44
+ RPCEndpoint,
32
45
  RPCId,
33
46
  RPCResponse,
34
47
  )
35
48
 
36
- DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 50.0
49
+ DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 30.0
37
50
 
38
51
 
39
52
  class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
40
53
  logger = logging.getLogger("web3.providers.PersistentConnectionProvider")
41
54
  has_persistent_connection = True
42
- endpoint_uri: Optional[str] = None
43
-
44
- _max_connection_retries: int = 5
45
55
 
46
- _ws: Optional[WebSocketClientProtocol] = None
47
56
  _request_processor: RequestProcessor
48
57
  _message_listener_task: Optional["asyncio.Task[None]"] = None
49
58
  _listen_event: asyncio.Event = asyncio.Event()
50
59
 
60
+ _batch_request_counter: Optional[int] = None
61
+
51
62
  def __init__(
52
63
  self,
53
64
  request_timeout: float = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT,
54
65
  subscription_response_queue_size: int = 500,
55
- request_information_cache_size: int = 500,
56
66
  silence_listener_task_exceptions: bool = False,
67
+ max_connection_retries: int = 5,
68
+ **kwargs: Any,
57
69
  ) -> None:
58
- super().__init__()
70
+ super().__init__(**kwargs)
59
71
  self._request_processor = RequestProcessor(
60
72
  self,
61
73
  subscription_response_queue_size=subscription_response_queue_size,
62
- request_information_cache_size=request_information_cache_size,
63
74
  )
64
75
  self.request_timeout = request_timeout
65
76
  self.silence_listener_task_exceptions = silence_listener_task_exceptions
77
+ self._max_connection_retries = max_connection_retries
78
+
79
+ def get_endpoint_uri_or_ipc_path(self) -> str:
80
+ if hasattr(self, "endpoint_uri"):
81
+ return str(self.endpoint_uri)
82
+ elif hasattr(self, "ipc_path"):
83
+ return str(self.ipc_path)
84
+ else:
85
+ raise Web3AttributeError(
86
+ "`PersistentConnectionProvider` must have either `endpoint_uri` or "
87
+ "`ipc_path` attribute."
88
+ )
66
89
 
67
90
  async def connect(self) -> None:
68
91
  _connection_attempts = 0
@@ -72,7 +95,9 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
72
95
  while _connection_attempts != self._max_connection_retries:
73
96
  try:
74
97
  _connection_attempts += 1
75
- self.logger.info(f"Connecting to: {self.endpoint_uri}")
98
+ self.logger.info(
99
+ f"Connecting to: {self.get_endpoint_uri_or_ipc_path()}"
100
+ )
76
101
  await self._provider_specific_connect()
77
102
  self._message_listener_task = asyncio.create_task(
78
103
  self._message_listener()
@@ -80,16 +105,18 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
80
105
  self._message_listener_task.add_done_callback(
81
106
  self._message_listener_callback
82
107
  )
83
- self.logger.info(f"Successfully connected to: {self.endpoint_uri}")
108
+ self.logger.info(
109
+ f"Successfully connected to: {self.get_endpoint_uri_or_ipc_path()}"
110
+ )
84
111
  break
85
112
  except (WebSocketException, OSError) as e:
86
113
  if _connection_attempts == self._max_connection_retries:
87
114
  raise ProviderConnectionError(
88
- f"Could not connect to: {self.endpoint_uri}. "
115
+ f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
89
116
  f"Retries exceeded max of {self._max_connection_retries}."
90
117
  ) from e
91
118
  self.logger.info(
92
- f"Could not connect to: {self.endpoint_uri}. "
119
+ f"Could not connect to: {self.get_endpoint_uri_or_ipc_path()}. "
93
120
  f"Retrying in {round(_backoff_time, 1)} seconds.",
94
121
  exc_info=True,
95
122
  )
@@ -109,7 +136,47 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
109
136
 
110
137
  await self._provider_specific_disconnect()
111
138
  self._request_processor.clear_caches()
112
- self.logger.info(f"Successfully disconnected from: {self.endpoint_uri}")
139
+ self.logger.info(
140
+ f"Successfully disconnected from: {self.get_endpoint_uri_or_ipc_path()}"
141
+ )
142
+
143
+ @async_handle_request_caching
144
+ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
145
+ request_data = self.encode_rpc_request(method, params)
146
+ await self.socket_send(request_data)
147
+
148
+ current_request_id = json.loads(request_data)["id"]
149
+ response = await self._get_response_for_request_id(current_request_id)
150
+
151
+ return response
152
+
153
+ async def make_batch_request(
154
+ self, requests: List[Tuple[RPCEndpoint, Any]]
155
+ ) -> List[RPCResponse]:
156
+ request_data = self.encode_batch_rpc_request(requests)
157
+ await self.socket_send(request_data)
158
+
159
+ response = cast(
160
+ List[RPCResponse], await self._get_response_for_request_id(BATCH_REQUEST_ID)
161
+ )
162
+ return response
163
+
164
+ # -- abstract methods -- #
165
+
166
+ @abstractmethod
167
+ async def socket_send(self, request_data: bytes) -> None:
168
+ """
169
+ Send an encoded RPC request to the provider over the persistent connection.
170
+ """
171
+ raise NotImplementedError("Must be implemented by subclasses")
172
+
173
+ @abstractmethod
174
+ async def socket_recv(self) -> RPCResponse:
175
+ """
176
+ Receive, decode, and return an RPC response from the provider over the
177
+ persistent connection.
178
+ """
179
+ raise NotImplementedError("Must be implemented by subclasses")
113
180
 
114
181
  # -- private methods -- #
115
182
 
@@ -119,7 +186,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
119
186
  async def _provider_specific_disconnect(self) -> None:
120
187
  raise NotImplementedError("Must be implemented by subclasses")
121
188
 
122
- async def _provider_specific_message_listener(self) -> None:
189
+ async def _provider_specific_socket_reader(self) -> RPCResponse:
123
190
  raise NotImplementedError("Must be implemented by subclasses")
124
191
 
125
192
  def _message_listener_callback(
@@ -141,14 +208,28 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
141
208
  # the use of sleep(0) seems to be the most efficient way to yield control
142
209
  # back to the event loop to share the loop with other tasks.
143
210
  await asyncio.sleep(0)
211
+
144
212
  try:
145
- await self._provider_specific_message_listener()
146
- except ConnectionClosedOK:
147
- # RequestManager._ws_message_stream() will rethrow this exception and
148
- # cause WebsocketConnection to stop the process_subscriptions() loop,
149
- # or raise ConnectionClosedOK when recv() is called.
150
- # see: https://github.com/ethereum/web3.py/pull/3424
151
- raise
213
+ response = await self._provider_specific_socket_reader()
214
+
215
+ if isinstance(response, list):
216
+ response = sort_batch_response_by_response_ids(response)
217
+
218
+ subscription = (
219
+ response.get("method") == "eth_subscription"
220
+ if not isinstance(response, list)
221
+ else False
222
+ )
223
+ await self._request_processor.cache_raw_response(
224
+ response, subscription=subscription
225
+ )
226
+ except PersistentConnectionClosedOK as e:
227
+ self.logger.info(
228
+ "Message listener background task has ended gracefully: "
229
+ f"{e.user_message}"
230
+ )
231
+ # trigger a return to end the listener task and initiate the callback fn
232
+ return
152
233
  except Exception as e:
153
234
  if not self.silence_listener_task_exceptions:
154
235
  raise e
@@ -182,7 +263,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
182
263
  raise msg_listener_task.exception()
183
264
 
184
265
  async def _get_response_for_request_id(
185
- self, request_id: RPCId, timeout: Optional[float] = None
266
+ self, request_id: Union[RPCId, List[RPCId]], timeout: Optional[float] = None
186
267
  ) -> RPCResponse:
187
268
  if timeout is None:
188
269
  timeout = self.request_timeout
@@ -0,0 +1,84 @@
1
+ from typing import (
2
+ TYPE_CHECKING,
3
+ Any,
4
+ Dict,
5
+ )
6
+
7
+ from web3.types import (
8
+ RPCEndpoint,
9
+ RPCResponse,
10
+ )
11
+
12
+ if TYPE_CHECKING:
13
+ from web3.main import ( # noqa: F401
14
+ AsyncWeb3,
15
+ )
16
+ from web3.manager import ( # noqa: F401
17
+ _AsyncPersistentMessageStream,
18
+ )
19
+
20
+
21
+ class PersistentConnection:
22
+ """
23
+ A class that houses the public API for interacting with the persistent connection
24
+ via a `AsyncWeb3` instance instantiated with a `PersistentConnectionProvider` class.
25
+ """
26
+
27
+ def __init__(self, w3: "AsyncWeb3"):
28
+ self._manager = w3.manager
29
+
30
+ @property
31
+ def subscriptions(self) -> Dict[str, Any]:
32
+ """
33
+ Return the active subscriptions on the persistent connection.
34
+
35
+ :return: The active subscriptions on the persistent connection.
36
+ :rtype: Dict[str, Any]
37
+ """
38
+ return self._manager._request_processor.active_subscriptions
39
+
40
+ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
41
+ """
42
+ Make a request to the persistent connection and return the response. This method
43
+ does not process the response as it would when invoking a method via the
44
+ appropriate module on the `AsyncWeb3` instance,
45
+ e.g. `w3.eth.get_block("latest")`.
46
+
47
+ :param method: The RPC method, e.g. `eth_getBlockByNumber`.
48
+ :param params: The RPC method parameters, e.g. `["0x1337", False]`.
49
+
50
+ :return: The processed response from the persistent connection.
51
+ :rtype: RPCResponse
52
+ """
53
+ return await self._manager.socket_request(method, params)
54
+
55
+ async def send(self, method: RPCEndpoint, params: Any) -> None:
56
+ """
57
+ Send a raw, unprocessed message to the persistent connection.
58
+
59
+ :param method: The RPC method, e.g. `eth_getBlockByNumber`.
60
+ :param params: The RPC method parameters, e.g. `["0x1337", False]`.
61
+
62
+ :return: None
63
+ """
64
+ await self._manager.send(method, params)
65
+
66
+ async def recv(self) -> RPCResponse:
67
+ """
68
+ Receive the next unprocessed response for a request from the persistent
69
+ connection.
70
+
71
+ :return: The next unprocessed response for a request from the persistent
72
+ connection.
73
+ :rtype: RPCResponse
74
+ """
75
+ return await self._manager.recv()
76
+
77
+ def process_subscriptions(self) -> "_AsyncPersistentMessageStream":
78
+ """
79
+ Asynchronous iterator that yields messages from the subscription message stream.
80
+
81
+ :return: The subscription message stream.
82
+ :rtype: _AsyncPersistentMessageStream
83
+ """
84
+ return self._manager._persistent_message_stream()