web3 7.0.0b5__py3-none-any.whl → 7.0.0b7__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 (82) hide show
  1. ens/__init__.py +13 -2
  2. web3/__init__.py +21 -5
  3. web3/_utils/batching.py +217 -0
  4. web3/_utils/caching.py +26 -2
  5. web3/_utils/compat/__init__.py +1 -0
  6. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  7. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  8. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  9. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  10. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  11. web3/_utils/contract_sources/contract_data/event_contracts.py +5 -5
  12. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  13. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  14. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  15. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  16. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  17. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  18. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  19. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  20. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  21. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  22. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  24. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  25. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  26. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  27. web3/_utils/events.py +2 -2
  28. web3/_utils/http.py +3 -0
  29. web3/_utils/http_session_manager.py +280 -0
  30. web3/_utils/method_formatters.py +0 -2
  31. web3/_utils/module_testing/eth_module.py +92 -119
  32. web3/_utils/module_testing/module_testing_utils.py +27 -9
  33. web3/_utils/module_testing/persistent_connection_provider.py +1 -0
  34. web3/_utils/module_testing/web3_module.py +438 -17
  35. web3/_utils/rpc_abi.py +0 -3
  36. web3/beacon/__init__.py +5 -0
  37. web3/beacon/async_beacon.py +9 -5
  38. web3/beacon/beacon.py +7 -5
  39. web3/contract/__init__.py +7 -0
  40. web3/contract/base_contract.py +10 -1
  41. web3/contract/utils.py +112 -4
  42. web3/eth/__init__.py +7 -0
  43. web3/eth/async_eth.py +5 -37
  44. web3/eth/eth.py +7 -57
  45. web3/exceptions.py +20 -0
  46. web3/gas_strategies/time_based.py +2 -2
  47. web3/main.py +21 -9
  48. web3/manager.py +113 -8
  49. web3/method.py +29 -9
  50. web3/middleware/__init__.py +17 -0
  51. web3/middleware/base.py +43 -0
  52. web3/module.py +47 -7
  53. web3/providers/__init__.py +21 -0
  54. web3/providers/async_base.py +55 -23
  55. web3/providers/base.py +59 -26
  56. web3/providers/eth_tester/__init__.py +5 -0
  57. web3/providers/eth_tester/defaults.py +0 -6
  58. web3/providers/eth_tester/middleware.py +3 -8
  59. web3/providers/ipc.py +23 -8
  60. web3/providers/legacy_websocket.py +26 -1
  61. web3/providers/persistent/__init__.py +7 -0
  62. web3/providers/persistent/async_ipc.py +60 -76
  63. web3/providers/persistent/persistent.py +134 -10
  64. web3/providers/persistent/request_processor.py +98 -14
  65. web3/providers/persistent/websocket.py +43 -66
  66. web3/providers/rpc/__init__.py +5 -0
  67. web3/providers/rpc/async_rpc.py +34 -12
  68. web3/providers/rpc/rpc.py +34 -12
  69. web3/providers/rpc/utils.py +0 -3
  70. web3/tools/benchmark/main.py +7 -6
  71. web3/tools/benchmark/node.py +1 -1
  72. web3/types.py +7 -1
  73. web3/utils/__init__.py +14 -5
  74. web3/utils/async_exception_handling.py +19 -7
  75. web3/utils/exception_handling.py +7 -5
  76. {web3-7.0.0b5.dist-info → web3-7.0.0b7.dist-info}/LICENSE +1 -1
  77. {web3-7.0.0b5.dist-info → web3-7.0.0b7.dist-info}/METADATA +33 -20
  78. {web3-7.0.0b5.dist-info → web3-7.0.0b7.dist-info}/RECORD +80 -80
  79. {web3-7.0.0b5.dist-info → web3-7.0.0b7.dist-info}/WHEEL +1 -1
  80. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  81. web3/_utils/request.py +0 -265
  82. {web3-7.0.0b5.dist-info → web3-7.0.0b7.dist-info}/top_level.txt +0 -0
web3/method.py CHANGED
@@ -10,7 +10,6 @@ from typing import (
10
10
  Sequence,
11
11
  Tuple,
12
12
  Type,
13
- TypeVar,
14
13
  Union,
15
14
  )
16
15
  import warnings
@@ -22,6 +21,9 @@ from eth_utils.toolz import (
22
21
  pipe,
23
22
  )
24
23
 
24
+ from web3._utils.batching import (
25
+ RPC_METHODS_UNSUPPORTED_DURING_BATCH,
26
+ )
25
27
  from web3._utils.method_formatters import (
26
28
  get_error_formatters,
27
29
  get_null_result_formatters,
@@ -32,17 +34,22 @@ from web3._utils.rpc_abi import (
32
34
  RPC,
33
35
  )
34
36
  from web3.exceptions import (
37
+ MethodNotSupported,
35
38
  Web3TypeError,
36
39
  Web3ValidationError,
37
40
  Web3ValueError,
38
41
  )
39
42
  from web3.types import (
40
43
  RPCEndpoint,
44
+ TFunc,
41
45
  TReturn,
42
46
  )
43
47
 
44
48
  if TYPE_CHECKING:
45
- from web3 import Web3 # noqa: F401
49
+ from web3 import ( # noqa: F401
50
+ PersistentConnectionProvider,
51
+ Web3,
52
+ )
46
53
  from web3.module import Module # noqa: F401
47
54
 
48
55
 
@@ -84,9 +91,6 @@ def default_root_munger(_module: "Module", *args: Any) -> List[Any]:
84
91
  return [*args]
85
92
 
86
93
 
87
- TFunc = TypeVar("TFunc", bound=Callable[..., Any])
88
-
89
-
90
94
  class Method(Generic[TFunc]):
91
95
  """
92
96
  Method object for web3 module methods
@@ -149,15 +153,31 @@ class Method(Generic[TFunc]):
149
153
  self.is_property = is_property
150
154
 
151
155
  def __get__(
152
- self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None
156
+ self,
157
+ module: Optional["Module"] = None,
158
+ _type: Optional[Type["Module"]] = None,
153
159
  ) -> TFunc:
154
- if obj is None:
160
+ self._module = module
161
+ if module is None:
155
162
  raise Web3TypeError(
156
163
  "Direct calls to methods are not supported. "
157
- "Methods must be called from an module instance, "
164
+ "Methods must be called from a module instance, "
158
165
  "usually attached to a web3 instance."
159
166
  )
160
- return obj.retrieve_caller_fn(self)
167
+
168
+ provider = module.w3.provider
169
+ if hasattr(provider, "_is_batching") and provider._is_batching:
170
+ if self.json_rpc_method in RPC_METHODS_UNSUPPORTED_DURING_BATCH:
171
+ raise MethodNotSupported(
172
+ f"Method `{self.json_rpc_method}` is not supported within a batch "
173
+ "request."
174
+ )
175
+ return module.retrieve_request_information(self)
176
+ else:
177
+ return module.retrieve_caller_fn(self)
178
+
179
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
180
+ return self.__get__(self._module)(*args, **kwargs)
161
181
 
162
182
  @property
163
183
  def method_selector_fn(
@@ -92,3 +92,20 @@ async def async_combine_middleware(
92
92
  initialized = mw(async_w3)
93
93
  accumulator_fn = await initialized.async_wrap_make_request(accumulator_fn)
94
94
  return accumulator_fn
95
+
96
+
97
+ __all__ = [
98
+ "AttributeDictMiddleware",
99
+ "Middleware",
100
+ "Web3Middleware",
101
+ "BufferedGasEstimateMiddleware",
102
+ "LocalFilterMiddleware",
103
+ "FormattingMiddlewareBuilder",
104
+ "GasPriceStrategyMiddleware",
105
+ "ENSNameToAddressMiddleware",
106
+ "ExtraDataToPOAMiddleware",
107
+ "PythonicMiddleware",
108
+ "SignAndSendRawMiddlewareBuilder",
109
+ "StalecheckMiddlewareBuilder",
110
+ "ValidationMiddleware",
111
+ ]
web3/middleware/base.py CHANGED
@@ -4,6 +4,8 @@ from abc import (
4
4
  from typing import (
5
5
  TYPE_CHECKING,
6
6
  Any,
7
+ List,
8
+ Tuple,
7
9
  Type,
8
10
  Union,
9
11
  )
@@ -18,7 +20,9 @@ if TYPE_CHECKING:
18
20
  Web3,
19
21
  )
20
22
  from web3.types import ( # noqa: F401
23
+ AsyncMakeBatchRequestFn,
21
24
  AsyncMakeRequestFn,
25
+ MakeBatchRequestFn,
22
26
  MakeRequestFn,
23
27
  RPCEndpoint,
24
28
  RPCResponse,
@@ -45,6 +49,25 @@ class Web3Middleware:
45
49
 
46
50
  return middleware
47
51
 
52
+ def wrap_make_batch_request(
53
+ self, make_batch_request: "MakeBatchRequestFn"
54
+ ) -> "MakeBatchRequestFn":
55
+ def middleware(
56
+ requests_info: List[Tuple["RPCEndpoint", Any]]
57
+ ) -> List["RPCResponse"]:
58
+ req_processed = [
59
+ self.request_processor(method, params)
60
+ for (method, params) in requests_info
61
+ ]
62
+ responses = make_batch_request(req_processed)
63
+ methods, _params = zip(*req_processed)
64
+ formatted_responses = [
65
+ self.response_processor(m, r) for m, r in zip(methods, responses)
66
+ ]
67
+ return formatted_responses
68
+
69
+ return middleware
70
+
48
71
  def request_processor(self, method: "RPCEndpoint", params: Any) -> Any:
49
72
  return method, params
50
73
 
@@ -67,6 +90,26 @@ class Web3Middleware:
67
90
 
68
91
  return middleware
69
92
 
93
+ async def async_wrap_make_batch_request(
94
+ self, make_batch_request: "AsyncMakeBatchRequestFn"
95
+ ) -> "AsyncMakeBatchRequestFn":
96
+ async def middleware(
97
+ requests_info: List[Tuple["RPCEndpoint", Any]]
98
+ ) -> List["RPCResponse"]:
99
+ req_processed = [
100
+ await self.async_request_processor(method, params)
101
+ for (method, params) in requests_info
102
+ ]
103
+ responses = await make_batch_request(req_processed)
104
+ methods, _params = zip(*req_processed)
105
+ formatted_responses = [
106
+ await self.async_response_processor(m, r)
107
+ for m, r in zip(methods, responses)
108
+ ]
109
+ return formatted_responses
110
+
111
+ return middleware
112
+
70
113
  async def async_request_processor(
71
114
  self,
72
115
  method: "RPCEndpoint",
web3/module.py CHANGED
@@ -5,6 +5,8 @@ from typing import (
5
5
  Coroutine,
6
6
  Dict,
7
7
  Optional,
8
+ Sequence,
9
+ Tuple,
8
10
  TypeVar,
9
11
  Union,
10
12
  cast,
@@ -55,9 +57,43 @@ def apply_result_formatters(
55
57
  TReturn = TypeVar("TReturn")
56
58
 
57
59
 
60
+ @curry
61
+ def retrieve_request_information_for_batching(
62
+ w3: Union["AsyncWeb3", "Web3"],
63
+ module: "Module",
64
+ method: Method[Callable[..., Any]],
65
+ ) -> Union[
66
+ Callable[..., Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]],
67
+ Callable[..., Coroutine[Any, Any, Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]]],
68
+ ]:
69
+ async def async_inner(
70
+ *args: Any, **kwargs: Any
71
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
72
+ (method_str, params), response_formatters = method.process_params(
73
+ module, *args, **kwargs
74
+ )
75
+ if isinstance(w3.provider, PersistentConnectionProvider):
76
+ w3.provider._request_processor.cache_request_information(
77
+ cast(RPCEndpoint, method_str), params, response_formatters
78
+ )
79
+ return (cast(RPCEndpoint, method_str), params), response_formatters
80
+
81
+ def inner(
82
+ *args: Any, **kwargs: Any
83
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
84
+ (method_str, params), response_formatters = method.process_params(
85
+ module, *args, **kwargs
86
+ )
87
+ return (cast(RPCEndpoint, method_str), params), response_formatters
88
+
89
+ return async_inner if module.is_async else inner
90
+
91
+
58
92
  @curry
59
93
  def retrieve_blocking_method_call_fn(
60
- w3: "Web3", module: "Module", method: Method[Callable[..., TReturn]]
94
+ w3: "Web3",
95
+ module: "Module",
96
+ method: Method[Callable[..., TReturn]],
61
97
  ) -> Callable[..., Union[TReturn, LogFilter]]:
62
98
  def caller(*args: Any, **kwargs: Any) -> Union[TReturn, LogFilter]:
63
99
  try:
@@ -82,7 +118,9 @@ def retrieve_blocking_method_call_fn(
82
118
 
83
119
  @curry
84
120
  def retrieve_async_method_call_fn(
85
- async_w3: "AsyncWeb3", module: "Module", method: Method[Callable[..., Any]]
121
+ async_w3: "AsyncWeb3",
122
+ module: "Module",
123
+ method: Method[Callable[..., Any]],
86
124
  ) -> Callable[..., Coroutine[Any, Any, Optional[Union[RPCResponse, AsyncLogFilter]]]]:
87
125
  async def caller(*args: Any, **kwargs: Any) -> Union[RPCResponse, AsyncLogFilter]:
88
126
  try:
@@ -93,12 +131,11 @@ def retrieve_async_method_call_fn(
93
131
  return AsyncLogFilter(eth_module=module, filter_id=err.filter_id)
94
132
 
95
133
  if isinstance(async_w3.provider, PersistentConnectionProvider):
96
- # TODO: The typing does not seem to be correct for response_formatters.
97
- # For now, keep the expected typing but ignore it here.
98
134
  provider = async_w3.provider
99
135
  cache_key = provider._request_processor.cache_request_information(
100
- cast(RPCEndpoint, method_str), params, response_formatters # type: ignore # noqa: E501
136
+ cast(RPCEndpoint, method_str), params, response_formatters
101
137
  )
138
+
102
139
  try:
103
140
  method_str = cast(RPCEndpoint, method_str)
104
141
  return await async_w3.manager.send(method_str, params)
@@ -139,6 +176,9 @@ class Module:
139
176
  self.retrieve_caller_fn = retrieve_async_method_call_fn(w3, self)
140
177
  else:
141
178
  self.retrieve_caller_fn = retrieve_blocking_method_call_fn(w3, self)
179
+ self.retrieve_request_information = retrieve_request_information_for_batching(
180
+ w3, self
181
+ )
142
182
  self.w3 = w3
143
183
 
144
184
  @property
@@ -152,8 +192,8 @@ class Module:
152
192
  ) -> None:
153
193
  for method_name, method_class in methods.items():
154
194
  klass = (
155
- method_class.__get__(obj=self)()
195
+ method_class.__get__(module=self)()
156
196
  if method_class.is_property
157
- else method_class.__get__(obj=self)
197
+ else method_class.__get__(module=self)
158
198
  )
159
199
  setattr(self, method_name, klass)
@@ -8,6 +8,10 @@ from .base import (
8
8
  BaseProvider,
9
9
  JSONBaseProvider,
10
10
  )
11
+ from .eth_tester import (
12
+ AsyncEthereumTesterProvider,
13
+ EthereumTesterProvider,
14
+ )
11
15
  from .ipc import (
12
16
  IPCProvider,
13
17
  )
@@ -26,3 +30,20 @@ from .persistent import (
26
30
  from .auto import (
27
31
  AutoProvider,
28
32
  )
33
+
34
+ __all__ = [
35
+ "AsyncBaseProvider",
36
+ "AsyncEthereumTesterProvider",
37
+ "AsyncHTTPProvider",
38
+ "AsyncIPCProvider",
39
+ "AutoProvider",
40
+ "BaseProvider",
41
+ "EthereumTesterProvider",
42
+ "HTTPProvider",
43
+ "IPCProvider",
44
+ "JSONBaseProvider",
45
+ "LegacyWebSocketProvider",
46
+ "PersistentConnection",
47
+ "PersistentConnectionProvider",
48
+ "WebSocketProvider",
49
+ ]
@@ -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__(self) -> None:
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
- def decode_rpc_response(self, raw_response: bytes) -> RPCResponse:
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__(self) -> None:
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
- def __init__(self) -> None:
119
- self.request_counter = itertools.count()
120
- super().__init__()
107
+ _is_batching: bool = False
108
+ _batch_request_func_cache: Tuple[
109
+ Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
110
+ ] = (None, None)
121
111
 
122
- def decode_rpc_response(self, raw_response: bytes) -> RPCResponse:
123
- text_response = to_text(raw_response)
124
- return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response))
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")
@@ -2,3 +2,8 @@ from .main import (
2
2
  AsyncEthereumTesterProvider,
3
3
  EthereumTesterProvider,
4
4
  )
5
+
6
+ __all__ = [
7
+ "AsyncEthereumTesterProvider",
8
+ "EthereumTesterProvider",
9
+ ]
@@ -243,12 +243,6 @@ API_ENDPOINTS = {
243
243
  "eth": {
244
244
  "protocolVersion": static_return(63),
245
245
  "syncing": static_return(False),
246
- "coinbase": compose(
247
- operator.itemgetter(0),
248
- call_eth_tester("get_accounts"),
249
- ),
250
- "mining": static_return(False),
251
- "hashrate": static_return(0),
252
246
  "chainId": static_return(131277322940537), # from fixture generation file
253
247
  "feeHistory": call_eth_tester("get_fee_history"),
254
248
  "maxPriorityFeePerGas": static_return(10**9),
@@ -59,7 +59,7 @@ if TYPE_CHECKING:
59
59
 
60
60
 
61
61
  def is_named_block(value: Any) -> bool:
62
- return value in {"latest", "earliest", "pending", "safe", "finalized"}
62
+ return value in {"latest", "earliest", "safe", "finalized"}
63
63
 
64
64
 
65
65
  def is_hexstr(value: Any) -> bool:
@@ -334,9 +334,7 @@ result_formatters: Optional[Dict[RPCEndpoint, Callable[..., Any]]] = {
334
334
 
335
335
 
336
336
  def guess_from(w3: "Web3", _: TxParams) -> ChecksumAddress:
337
- if w3.eth.coinbase:
338
- return w3.eth.coinbase
339
- elif w3.eth.accounts and len(w3.eth.accounts) > 0:
337
+ if w3.eth.accounts and len(w3.eth.accounts) > 0:
340
338
  return w3.eth.accounts[0]
341
339
 
342
340
  return None
@@ -360,11 +358,8 @@ def fill_default(
360
358
  async def async_guess_from(
361
359
  async_w3: "AsyncWeb3", _: TxParams
362
360
  ) -> Optional[ChecksumAddress]:
363
- coinbase = await async_w3.eth.coinbase
364
361
  accounts = await async_w3.eth.accounts
365
- if coinbase is not None:
366
- return coinbase
367
- elif accounts is not None and len(accounts) > 0:
362
+ if accounts is not None and len(accounts) > 0:
368
363
  return accounts[0]
369
364
  return None
370
365