web3 7.0.0b2__py3-none-any.whl → 7.7.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 (144) hide show
  1. ens/__init__.py +13 -2
  2. ens/_normalization.py +4 -4
  3. ens/async_ens.py +27 -15
  4. ens/base_ens.py +3 -1
  5. ens/contract_data.py +2 -2
  6. ens/ens.py +10 -7
  7. ens/exceptions.py +16 -29
  8. ens/specs/nf.json +1 -1
  9. ens/specs/normalization_spec.json +1 -1
  10. ens/utils.py +24 -32
  11. web3/__init__.py +23 -12
  12. web3/_utils/abi.py +157 -263
  13. web3/_utils/async_transactions.py +34 -20
  14. web3/_utils/batching.py +217 -0
  15. web3/_utils/blocks.py +6 -2
  16. web3/_utils/caching/__init__.py +12 -0
  17. web3/_utils/caching/caching_utils.py +433 -0
  18. web3/_utils/caching/request_caching_validation.py +287 -0
  19. web3/_utils/compat/__init__.py +2 -3
  20. web3/_utils/contract_sources/compile_contracts.py +1 -1
  21. web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +42 -0
  22. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  24. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  25. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  26. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  27. web3/_utils/contract_sources/contract_data/event_contracts.py +50 -5
  28. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  29. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  30. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  31. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  32. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  33. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  34. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  35. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  36. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  37. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  38. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  39. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  40. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  41. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  42. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  43. web3/_utils/contracts.py +172 -220
  44. web3/_utils/datatypes.py +5 -1
  45. web3/_utils/decorators.py +6 -1
  46. web3/_utils/empty.py +1 -1
  47. web3/_utils/encoding.py +16 -12
  48. web3/_utils/error_formatters_utils.py +5 -3
  49. web3/_utils/events.py +78 -72
  50. web3/_utils/fee_utils.py +1 -3
  51. web3/_utils/filters.py +24 -22
  52. web3/_utils/formatters.py +2 -2
  53. web3/_utils/http.py +8 -2
  54. web3/_utils/http_session_manager.py +314 -0
  55. web3/_utils/math.py +14 -15
  56. web3/_utils/method_formatters.py +161 -34
  57. web3/_utils/module.py +2 -1
  58. web3/_utils/module_testing/__init__.py +3 -2
  59. web3/_utils/module_testing/eth_module.py +736 -583
  60. web3/_utils/module_testing/go_ethereum_debug_module.py +128 -0
  61. web3/_utils/module_testing/module_testing_utils.py +81 -24
  62. web3/_utils/module_testing/persistent_connection_provider.py +702 -220
  63. web3/_utils/module_testing/utils.py +114 -33
  64. web3/_utils/module_testing/web3_module.py +438 -17
  65. web3/_utils/normalizers.py +13 -11
  66. web3/_utils/rpc_abi.py +10 -22
  67. web3/_utils/threads.py +8 -7
  68. web3/_utils/transactions.py +32 -25
  69. web3/_utils/type_conversion.py +5 -1
  70. web3/_utils/validation.py +20 -17
  71. web3/beacon/__init__.py +5 -0
  72. web3/beacon/api_endpoints.py +3 -0
  73. web3/beacon/async_beacon.py +29 -6
  74. web3/beacon/beacon.py +24 -6
  75. web3/contract/__init__.py +7 -0
  76. web3/contract/async_contract.py +285 -82
  77. web3/contract/base_contract.py +556 -258
  78. web3/contract/contract.py +295 -84
  79. web3/contract/utils.py +251 -55
  80. web3/datastructures.py +49 -34
  81. web3/eth/__init__.py +7 -0
  82. web3/eth/async_eth.py +89 -69
  83. web3/eth/base_eth.py +7 -3
  84. web3/eth/eth.py +43 -66
  85. web3/exceptions.py +158 -83
  86. web3/gas_strategies/time_based.py +8 -6
  87. web3/geth.py +53 -184
  88. web3/main.py +77 -17
  89. web3/manager.py +362 -95
  90. web3/method.py +43 -15
  91. web3/middleware/__init__.py +17 -0
  92. web3/middleware/attrdict.py +12 -22
  93. web3/middleware/base.py +55 -2
  94. web3/middleware/filter.py +45 -23
  95. web3/middleware/formatting.py +6 -3
  96. web3/middleware/names.py +4 -1
  97. web3/middleware/signing.py +15 -6
  98. web3/middleware/stalecheck.py +2 -1
  99. web3/module.py +61 -25
  100. web3/providers/__init__.py +21 -0
  101. web3/providers/async_base.py +87 -32
  102. web3/providers/base.py +77 -32
  103. web3/providers/eth_tester/__init__.py +5 -0
  104. web3/providers/eth_tester/defaults.py +2 -55
  105. web3/providers/eth_tester/main.py +41 -15
  106. web3/providers/eth_tester/middleware.py +16 -17
  107. web3/providers/ipc.py +41 -17
  108. web3/providers/legacy_websocket.py +26 -1
  109. web3/providers/persistent/__init__.py +7 -0
  110. web3/providers/persistent/async_ipc.py +61 -121
  111. web3/providers/persistent/persistent.py +323 -16
  112. web3/providers/persistent/persistent_connection.py +54 -5
  113. web3/providers/persistent/request_processor.py +136 -56
  114. web3/providers/persistent/subscription_container.py +56 -0
  115. web3/providers/persistent/subscription_manager.py +233 -0
  116. web3/providers/persistent/websocket.py +29 -92
  117. web3/providers/rpc/__init__.py +5 -0
  118. web3/providers/rpc/async_rpc.py +73 -18
  119. web3/providers/rpc/rpc.py +73 -30
  120. web3/providers/rpc/utils.py +1 -13
  121. web3/scripts/install_pre_releases.py +33 -0
  122. web3/scripts/parse_pygeth_version.py +16 -0
  123. web3/testing.py +4 -4
  124. web3/tracing.py +9 -5
  125. web3/types.py +141 -74
  126. web3/utils/__init__.py +64 -5
  127. web3/utils/abi.py +790 -10
  128. web3/utils/address.py +8 -0
  129. web3/utils/async_exception_handling.py +20 -11
  130. web3/utils/caching.py +34 -4
  131. web3/utils/exception_handling.py +9 -12
  132. web3/utils/subscriptions.py +285 -0
  133. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/LICENSE +1 -1
  134. web3-7.7.0.dist-info/METADATA +130 -0
  135. web3-7.7.0.dist-info/RECORD +171 -0
  136. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/WHEEL +1 -1
  137. web3/_utils/caching.py +0 -155
  138. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  139. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
  140. web3/_utils/request.py +0 -265
  141. web3-7.0.0b2.dist-info/METADATA +0 -106
  142. web3-7.0.0b2.dist-info/RECORD +0 -163
  143. /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
  144. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/top_level.txt +0 -0
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,
@@ -30,6 +32,7 @@ from web3.providers.persistent import (
30
32
  PersistentConnectionProvider,
31
33
  )
32
34
  from web3.types import (
35
+ FormattedEthSubscriptionResponse,
33
36
  RPCEndpoint,
34
37
  RPCResponse,
35
38
  )
@@ -55,9 +58,43 @@ def apply_result_formatters(
55
58
  TReturn = TypeVar("TReturn")
56
59
 
57
60
 
61
+ @curry
62
+ def retrieve_request_information_for_batching(
63
+ w3: Union["AsyncWeb3", "Web3"],
64
+ module: "Module",
65
+ method: Method[Callable[..., Any]],
66
+ ) -> Union[
67
+ Callable[..., Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]],
68
+ Callable[..., Coroutine[Any, Any, Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]]],
69
+ ]:
70
+ async def async_inner(
71
+ *args: Any, **kwargs: Any
72
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
73
+ (method_str, params), response_formatters = method.process_params(
74
+ module, *args, **kwargs
75
+ )
76
+ if isinstance(w3.provider, PersistentConnectionProvider):
77
+ w3.provider._request_processor.cache_request_information(
78
+ None, cast(RPCEndpoint, method_str), params, response_formatters
79
+ )
80
+ return (cast(RPCEndpoint, method_str), params), response_formatters
81
+
82
+ def inner(
83
+ *args: Any, **kwargs: Any
84
+ ) -> Tuple[Tuple[RPCEndpoint, Any], Sequence[Any]]:
85
+ (method_str, params), response_formatters = method.process_params(
86
+ module, *args, **kwargs
87
+ )
88
+ return (cast(RPCEndpoint, method_str), params), response_formatters
89
+
90
+ return async_inner if module.is_async else inner
91
+
92
+
58
93
  @curry
59
94
  def retrieve_blocking_method_call_fn(
60
- w3: "Web3", module: "Module", method: Method[Callable[..., TReturn]]
95
+ w3: "Web3",
96
+ module: "Module",
97
+ method: Method[Callable[..., TReturn]],
61
98
  ) -> Callable[..., Union[TReturn, LogFilter]]:
62
99
  def caller(*args: Any, **kwargs: Any) -> Union[TReturn, LogFilter]:
63
100
  try:
@@ -82,9 +119,20 @@ def retrieve_blocking_method_call_fn(
82
119
 
83
120
  @curry
84
121
  def retrieve_async_method_call_fn(
85
- async_w3: "AsyncWeb3", module: "Module", method: Method[Callable[..., Any]]
86
- ) -> Callable[..., Coroutine[Any, Any, Optional[Union[RPCResponse, AsyncLogFilter]]]]:
87
- async def caller(*args: Any, **kwargs: Any) -> Union[RPCResponse, AsyncLogFilter]:
122
+ async_w3: "AsyncWeb3",
123
+ module: "Module",
124
+ method: Method[Callable[..., Any]],
125
+ ) -> Callable[
126
+ ...,
127
+ Coroutine[
128
+ Any,
129
+ Any,
130
+ Optional[Union[RPCResponse, FormattedEthSubscriptionResponse, AsyncLogFilter]],
131
+ ],
132
+ ]:
133
+ async def caller(
134
+ *args: Any, **kwargs: Any
135
+ ) -> Union[RPCResponse, FormattedEthSubscriptionResponse, AsyncLogFilter]:
88
136
  try:
89
137
  (method_str, params), response_formatters = method.process_params(
90
138
  module, *args, **kwargs
@@ -93,32 +141,17 @@ def retrieve_async_method_call_fn(
93
141
  return AsyncLogFilter(eth_module=module, filter_id=err.filter_id)
94
142
 
95
143
  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
- provider = async_w3.provider
99
- cache_key = provider._request_processor.cache_request_information(
100
- cast(RPCEndpoint, method_str), params, response_formatters # type: ignore # noqa: E501
144
+ return await async_w3.manager.socket_request(
145
+ cast(RPCEndpoint, method_str),
146
+ params,
147
+ response_formatters=response_formatters,
101
148
  )
102
- try:
103
- method_str = cast(RPCEndpoint, method_str)
104
- return await async_w3.manager.send(method_str, params)
105
- except Exception as e:
106
- if (
107
- cache_key is not None
108
- and cache_key
109
- in provider._request_processor._request_information_cache
110
- ):
111
- provider._request_processor.pop_cached_request_information(
112
- cache_key
113
- )
114
- raise e
115
149
  else:
116
150
  (
117
151
  result_formatters,
118
152
  error_formatters,
119
153
  null_result_formatters,
120
154
  ) = response_formatters
121
-
122
155
  result = await async_w3.manager.coro_request(
123
156
  method_str, params, error_formatters, null_result_formatters
124
157
  )
@@ -139,6 +172,9 @@ class Module:
139
172
  self.retrieve_caller_fn = retrieve_async_method_call_fn(w3, self)
140
173
  else:
141
174
  self.retrieve_caller_fn = retrieve_blocking_method_call_fn(w3, self)
175
+ self.retrieve_request_information = retrieve_request_information_for_batching(
176
+ w3, self
177
+ )
142
178
  self.w3 = w3
143
179
 
144
180
  @property
@@ -152,8 +188,8 @@ class Module:
152
188
  ) -> None:
153
189
  for method_name, method_class in methods.items():
154
190
  klass = (
155
- method_class.__get__(obj=self)()
191
+ method_class.__get__(module=self)()
156
192
  if method_class.is_property
157
- else method_class.__get__(obj=self)
193
+ else method_class.__get__(module=self)
158
194
  )
159
195
  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
+ ]
@@ -1,13 +1,17 @@
1
1
  import asyncio
2
2
  import itertools
3
+ import logging
3
4
  from typing import (
4
5
  TYPE_CHECKING,
5
6
  Any,
6
7
  Callable,
7
8
  Coroutine,
9
+ Dict,
10
+ List,
8
11
  Optional,
9
12
  Set,
10
13
  Tuple,
14
+ Union,
11
15
  cast,
12
16
  )
13
17
 
@@ -18,7 +22,11 @@ from eth_utils import (
18
22
  )
19
23
 
20
24
  from web3._utils.caching import (
21
- async_handle_request_caching,
25
+ CACHEABLE_REQUESTS,
26
+ )
27
+ from web3._utils.empty import (
28
+ Empty,
29
+ empty,
22
30
  )
23
31
  from web3._utils.encoding import (
24
32
  FriendlyJsonSerde,
@@ -36,9 +44,11 @@ from web3.middleware.base import (
36
44
  )
37
45
  from web3.types import (
38
46
  RPCEndpoint,
47
+ RPCRequest,
39
48
  RPCResponse,
40
49
  )
41
50
  from web3.utils import (
51
+ RequestCacheValidationThreshold,
42
52
  SimpleCache,
43
53
  )
44
54
 
@@ -56,41 +66,39 @@ if TYPE_CHECKING:
56
66
  )
57
67
 
58
68
 
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
69
  class AsyncBaseProvider:
70
+ # Set generic logger for the provider. Override in subclasses for more specificity.
71
+ logger: logging.Logger = logging.getLogger(
72
+ "web3.providers.async_base.AsyncBaseProvider"
73
+ )
77
74
  _request_func_cache: Tuple[
78
75
  Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, RPCResponse]]
79
76
  ] = (None, None)
80
77
 
78
+ _is_batching: bool = False
79
+ _batch_request_func_cache: Tuple[
80
+ Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, List[RPCResponse]]]
81
+ ] = (None, None)
82
+
81
83
  is_async = True
82
84
  has_persistent_connection = False
83
85
  global_ccip_read_enabled: bool = True
84
86
  ccip_read_max_redirects: int = 4
85
87
 
86
- # request caching
87
- cache_allowed_requests: bool = False
88
- cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
89
- _request_cache: SimpleCache
90
- _request_cache_lock: asyncio.Lock = asyncio.Lock()
91
-
92
- def __init__(self) -> None:
88
+ def __init__(
89
+ self,
90
+ cache_allowed_requests: bool = False,
91
+ cacheable_requests: Set[RPCEndpoint] = None,
92
+ request_cache_validation_threshold: Optional[
93
+ Union[RequestCacheValidationThreshold, int, Empty]
94
+ ] = empty,
95
+ ) -> None:
93
96
  self._request_cache = SimpleCache(1000)
97
+ self._request_cache_lock: asyncio.Lock = asyncio.Lock()
98
+
99
+ self.cache_allowed_requests = cache_allowed_requests
100
+ self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
101
+ self.request_cache_validation_threshold = request_cache_validation_threshold
94
102
 
95
103
  async def request_func(
96
104
  self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
@@ -109,10 +117,33 @@ class AsyncBaseProvider:
109
117
  )
110
118
  return self._request_func_cache[-1]
111
119
 
112
- @async_handle_request_caching
120
+ async def batch_request_func(
121
+ self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
122
+ ) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
123
+ middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
124
+
125
+ cache_key = self._batch_request_func_cache[0]
126
+ if cache_key != middleware:
127
+ accumulator_fn = self.make_batch_request
128
+ for mw in reversed(middleware):
129
+ initialized = mw(async_w3)
130
+ # type ignore bc in order to wrap the method, we have to call
131
+ # `async_wrap_make_batch_request` with the accumulator_fn as the
132
+ # argument which breaks the type hinting for this particular case.
133
+ accumulator_fn = await initialized.async_wrap_make_batch_request( # type: ignore # noqa: E501
134
+ accumulator_fn
135
+ )
136
+ self._batch_request_func_cache = (middleware, accumulator_fn)
137
+ return self._batch_request_func_cache[-1]
138
+
113
139
  async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
114
140
  raise NotImplementedError("Providers must implement this method")
115
141
 
142
+ async def make_batch_request(
143
+ self, requests: List[Tuple[RPCEndpoint, Any]]
144
+ ) -> List[RPCResponse]:
145
+ raise NotImplementedError("Only AsyncHTTPProvider supports this method")
146
+
116
147
  async def is_connected(self, show_traceback: bool = False) -> bool:
117
148
  raise NotImplementedError("Providers must implement this method")
118
149
 
@@ -141,22 +172,33 @@ class AsyncBaseProvider:
141
172
 
142
173
 
143
174
  class AsyncJSONBaseProvider(AsyncBaseProvider):
144
- def __init__(self) -> None:
145
- super().__init__()
175
+ def __init__(self, **kwargs: Any) -> None:
176
+ super().__init__(**kwargs)
146
177
  self.request_counter = itertools.count()
147
178
 
148
- def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
179
+ def form_request(self, method: RPCEndpoint, params: Any = None) -> RPCRequest:
149
180
  request_id = next(self.request_counter)
150
181
  rpc_dict = {
182
+ "id": request_id,
151
183
  "jsonrpc": "2.0",
152
184
  "method": method,
153
185
  "params": params or [],
154
- "id": request_id,
155
186
  }
156
- encoded = FriendlyJsonSerde().json_encode(rpc_dict, cls=Web3JsonEncoder)
187
+ return cast(RPCRequest, rpc_dict)
188
+
189
+ @staticmethod
190
+ def encode_rpc_dict(rpc_dict: RPCRequest) -> bytes:
191
+ encoded = FriendlyJsonSerde().json_encode(
192
+ cast(Dict[str, Any], rpc_dict), cls=Web3JsonEncoder
193
+ )
157
194
  return to_bytes(text=encoded)
158
195
 
159
- def decode_rpc_response(self, raw_response: bytes) -> RPCResponse:
196
+ def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
197
+ rpc_dict = self.form_request(method, params)
198
+ return self.encode_rpc_dict(rpc_dict)
199
+
200
+ @staticmethod
201
+ def decode_rpc_response(raw_response: bytes) -> RPCResponse:
160
202
  text_response = str(
161
203
  to_text(raw_response) if not is_text(raw_response) else raw_response
162
204
  )
@@ -185,3 +227,16 @@ class AsyncJSONBaseProvider(AsyncBaseProvider):
185
227
  if show_traceback:
186
228
  raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
187
229
  return False
230
+
231
+ # -- batch requests -- #
232
+
233
+ def encode_batch_rpc_request(
234
+ self, requests: List[Tuple[RPCEndpoint, Any]]
235
+ ) -> bytes:
236
+ return (
237
+ b"["
238
+ + b", ".join(
239
+ self.encode_rpc_request(method, params) for method, params in requests
240
+ )
241
+ + b"]"
242
+ )
web3/providers/base.py CHANGED
@@ -1,11 +1,15 @@
1
1
  import itertools
2
+ import logging
2
3
  import threading
3
4
  from typing import (
4
5
  TYPE_CHECKING,
5
6
  Any,
6
7
  Callable,
8
+ List,
9
+ Optional,
7
10
  Set,
8
11
  Tuple,
12
+ Union,
9
13
  cast,
10
14
  )
11
15
 
@@ -15,7 +19,11 @@ from eth_utils import (
15
19
  )
16
20
 
17
21
  from web3._utils.caching import (
18
- handle_request_caching,
22
+ CACHEABLE_REQUESTS,
23
+ )
24
+ from web3._utils.empty import (
25
+ Empty,
26
+ empty,
19
27
  )
20
28
  from web3._utils.encoding import (
21
29
  FriendlyJsonSerde,
@@ -36,6 +44,7 @@ from web3.types import (
36
44
  RPCResponse,
37
45
  )
38
46
  from web3.utils import (
47
+ RequestCacheValidationThreshold,
39
48
  SimpleCache,
40
49
  )
41
50
 
@@ -43,24 +52,9 @@ if TYPE_CHECKING:
43
52
  from web3 import Web3 # noqa: F401
44
53
 
45
54
 
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
55
  class BaseProvider:
56
+ # Set generic logger for the provider. Override in subclasses for more specificity.
57
+ logger: logging.Logger = logging.getLogger("web3.providers.base.BaseProvider")
64
58
  # a tuple of (middleware, request_func)
65
59
  _request_func_cache: Tuple[Tuple[Middleware, ...], Callable[..., RPCResponse]] = (
66
60
  None,
@@ -72,14 +66,20 @@ class BaseProvider:
72
66
  global_ccip_read_enabled: bool = True
73
67
  ccip_read_max_redirects: int = 4
74
68
 
75
- # request caching
76
- cache_allowed_requests: bool = False
77
- cacheable_requests: Set[RPCEndpoint] = CACHEABLE_REQUESTS
78
- _request_cache: SimpleCache
79
- _request_cache_lock: threading.Lock = threading.Lock()
80
-
81
- def __init__(self) -> None:
69
+ def __init__(
70
+ self,
71
+ cache_allowed_requests: bool = False,
72
+ cacheable_requests: Set[RPCEndpoint] = None,
73
+ request_cache_validation_threshold: Optional[
74
+ Union[RequestCacheValidationThreshold, int, Empty]
75
+ ] = empty,
76
+ ) -> None:
82
77
  self._request_cache = SimpleCache(1000)
78
+ self._request_cache_lock: threading.Lock = threading.Lock()
79
+
80
+ self.cache_allowed_requests = cache_allowed_requests
81
+ self.cacheable_requests = cacheable_requests or CACHEABLE_REQUESTS
82
+ self.request_cache_validation_threshold = request_cache_validation_threshold
83
83
 
84
84
  def request_func(
85
85
  self, w3: "Web3", middleware_onion: MiddlewareOnion
@@ -106,7 +106,6 @@ class BaseProvider:
106
106
 
107
107
  return self._request_func_cache[-1]
108
108
 
109
- @handle_request_caching
110
109
  def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
111
110
  raise NotImplementedError("Providers must implement this method")
112
111
 
@@ -115,13 +114,16 @@ class BaseProvider:
115
114
 
116
115
 
117
116
  class JSONBaseProvider(BaseProvider):
118
- def __init__(self) -> None:
119
- self.request_counter = itertools.count()
120
- super().__init__()
117
+ logger = logging.getLogger("web3.providers.base.JSONBaseProvider")
121
118
 
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))
119
+ _is_batching: bool = False
120
+ _batch_request_func_cache: Tuple[
121
+ Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
122
+ ] = (None, None)
123
+
124
+ def __init__(self, **kwargs: Any) -> None:
125
+ super().__init__(**kwargs)
126
+ self.request_counter = itertools.count()
125
127
 
126
128
  def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes:
127
129
  rpc_dict = {
@@ -133,6 +135,11 @@ class JSONBaseProvider(BaseProvider):
133
135
  encoded = FriendlyJsonSerde().json_encode(rpc_dict, Web3JsonEncoder)
134
136
  return to_bytes(text=encoded)
135
137
 
138
+ @staticmethod
139
+ def decode_rpc_response(raw_response: bytes) -> RPCResponse:
140
+ text_response = to_text(raw_response)
141
+ return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response))
142
+
136
143
  def is_connected(self, show_traceback: bool = False) -> bool:
137
144
  try:
138
145
  response = self.make_request(RPCEndpoint("web3_clientVersion"), [])
@@ -156,3 +163,41 @@ class JSONBaseProvider(BaseProvider):
156
163
  if show_traceback:
157
164
  raise ProviderConnectionError(f"Bad jsonrpc version: {response}")
158
165
  return False
166
+
167
+ # -- batch requests -- #
168
+
169
+ def batch_request_func(
170
+ self, w3: "Web3", middleware_onion: MiddlewareOnion
171
+ ) -> Callable[..., List[RPCResponse]]:
172
+ middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
173
+
174
+ cache_key = self._batch_request_func_cache[0]
175
+ if cache_key != middleware:
176
+ accumulator_fn = self.make_batch_request
177
+ for mw in reversed(middleware):
178
+ initialized = mw(w3)
179
+ # type ignore bc in order to wrap the method, we have to call
180
+ # `wrap_make_batch_request` with the accumulator_fn as the argument
181
+ # which breaks the type hinting for this particular case.
182
+ accumulator_fn = initialized.wrap_make_batch_request(
183
+ accumulator_fn
184
+ ) # type: ignore # noqa: E501
185
+ self._batch_request_func_cache = (middleware, accumulator_fn)
186
+
187
+ return self._batch_request_func_cache[-1]
188
+
189
+ def encode_batch_rpc_request(
190
+ self, requests: List[Tuple[RPCEndpoint, Any]]
191
+ ) -> bytes:
192
+ return (
193
+ b"["
194
+ + b", ".join(
195
+ self.encode_rpc_request(method, params) for method, params in requests
196
+ )
197
+ + b"]"
198
+ )
199
+
200
+ def make_batch_request(
201
+ self, requests: List[Tuple[RPCEndpoint, Any]]
202
+ ) -> List[RPCResponse]:
203
+ 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
+ ]
@@ -21,7 +21,6 @@ from eth_tester.exceptions import (
21
21
  FilterNotFound,
22
22
  TransactionFailed,
23
23
  TransactionNotFound,
24
- ValidationError,
25
24
  )
26
25
  from eth_typing import (
27
26
  HexAddress,
@@ -218,7 +217,7 @@ def _generate_random_private_key() -> HexStr:
218
217
  WARNING: This is not a secure way to generate private keys and should only
219
218
  be used for testing purposes.
220
219
  """
221
- return encode_hex(bytes(bytearray((random.randint(0, 255) for _ in range(32)))))
220
+ return encode_hex(bytes(bytearray(random.randint(0, 255) for _ in range(32))))
222
221
 
223
222
 
224
223
  @without_params
@@ -226,18 +225,6 @@ def create_new_account(eth_tester: "EthereumTester") -> HexAddress:
226
225
  return eth_tester.add_account(_generate_random_private_key())
227
226
 
228
227
 
229
- def personal_send_transaction(eth_tester: "EthereumTester", params: Any) -> HexStr:
230
- transaction, password = params
231
-
232
- try:
233
- eth_tester.unlock_account(transaction["from"], password)
234
- transaction_hash = eth_tester.send_transaction(transaction)
235
- finally:
236
- eth_tester.lock_account(transaction["from"])
237
-
238
- return transaction_hash
239
-
240
-
241
228
  API_ENDPOINTS = {
242
229
  "web3": {
243
230
  "clientVersion": client_version,
@@ -256,15 +243,10 @@ API_ENDPOINTS = {
256
243
  "eth": {
257
244
  "protocolVersion": static_return(63),
258
245
  "syncing": static_return(False),
259
- "coinbase": compose(
260
- operator.itemgetter(0),
261
- call_eth_tester("get_accounts"),
262
- ),
263
- "mining": static_return(False),
264
- "hashrate": static_return(0),
265
246
  "chainId": static_return(131277322940537), # from fixture generation file
266
247
  "feeHistory": call_eth_tester("get_fee_history"),
267
248
  "maxPriorityFeePerGas": static_return(10**9),
249
+ "blobBaseFee": static_return(10**9),
268
250
  "gasPrice": static_return(10**9), # must be >= base fee post-London
269
251
  "accounts": call_eth_tester("get_accounts"),
270
252
  "blockNumber": compose(
@@ -404,41 +386,6 @@ API_ENDPOINTS = {
404
386
  "writeBlockProfile": not_implemented,
405
387
  "writeMemProfile": not_implemented,
406
388
  },
407
- "personal": {
408
- "ec_recover": not_implemented,
409
- "import_raw_key": call_eth_tester("add_account"),
410
- "list_accounts": call_eth_tester("get_accounts"),
411
- "list_wallets": not_implemented,
412
- "lock_account": excepts(
413
- ValidationError,
414
- compose(static_return(True), call_eth_tester("lock_account")),
415
- static_return(False),
416
- ),
417
- "new_account": create_new_account,
418
- "unlock_account": excepts(
419
- ValidationError,
420
- compose(static_return(True), call_eth_tester("unlock_account")),
421
- static_return(False),
422
- ),
423
- "send_transaction": personal_send_transaction,
424
- "sign": not_implemented,
425
- # deprecated
426
- "ecRecover": not_implemented,
427
- "importRawKey": call_eth_tester("add_account"),
428
- "listAccounts": call_eth_tester("get_accounts"),
429
- "lockAccount": excepts(
430
- ValidationError,
431
- compose(static_return(True), call_eth_tester("lock_account")),
432
- static_return(False),
433
- ),
434
- "newAccount": create_new_account,
435
- "unlockAccount": excepts(
436
- ValidationError,
437
- compose(static_return(True), call_eth_tester("unlock_account")),
438
- static_return(False),
439
- ),
440
- "sendTransaction": personal_send_transaction,
441
- },
442
389
  "testing": {
443
390
  "timeTravel": call_eth_tester("time_travel"),
444
391
  },