solver-multirpc 3.1.10__tar.gz → 3.1.12__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solver-multirpc
3
- Version: 3.1.10
3
+ Version: 3.1.12
4
4
  Summary: A robust Python library for interacting with Ethereum smart contracts via multiple RPC endpoints, ensuring reliability, availability, and load distribution with automatic retries on failure.
5
5
  License: MIT
6
6
  Author: rorschach
@@ -34,12 +34,13 @@ class AsyncMultiRpc(BaseMultiRpc):
34
34
  is_proof_authority: bool = False,
35
35
  multicall_custom_address: str = None,
36
36
  log_level: logging = logging.WARN,
37
- setup_on_init: bool = True
37
+ setup_on_init: bool = True,
38
+ is_flash_block_aware: Optional[bool] = None
38
39
  ):
39
40
  super().__init__(rpc_urls, contract_address, contract_abi, rpcs_supporting_tx_trace,
40
41
  view_policy, gas_estimation, gas_limit,
41
42
  gas_upper_bound, apm, enable_estimate_gas_limit,
42
- is_proof_authority, multicall_custom_address, log_level)
43
+ is_proof_authority, multicall_custom_address, log_level, is_flash_block_aware)
43
44
 
44
45
  for func_abi in self.contract_abi:
45
46
  if func_abi.get("stateMutability") in ("view", "pure"):
@@ -55,13 +56,14 @@ class AsyncMultiRpc(BaseMultiRpc):
55
56
  if setup_on_init:
56
57
  asyncio.run(self.setup())
57
58
 
58
- async def get_nonce(self, address: Union[Address, ChecksumAddress, str]) -> int:
59
- return await super()._get_nonce(address)
59
+ async def get_nonce(self, address: Union[Address, ChecksumAddress, str],
60
+ block_identifier: BlockIdentifier = None) -> int:
61
+ return await super()._get_nonce(address, block_identifier)
60
62
 
61
63
  async def get_tx_receipt(self, tx_hash) -> TxReceipt:
62
64
  return await super().get_tx_receipt(tx_hash)
63
65
 
64
- async def get_block(self, block_identifier: BlockIdentifier = 'latest',
66
+ async def get_block(self, block_identifier: BlockIdentifier = None,
65
67
  full_transactions: bool = False) -> BlockData:
66
68
  return await super().get_block(block_identifier, full_transactions)
67
69
 
@@ -84,7 +86,7 @@ class AsyncMultiRpc(BaseMultiRpc):
84
86
  wait_for_receipt: int = 90,
85
87
  priority: TxPriority = TxPriority.Low,
86
88
  gas_estimation_method: GasEstimationMethod = None,
87
- block_identifier: Union[str, int] = 'latest',
89
+ block_identifier: Union[str, int] = None,
88
90
  enable_estimate_gas_limit: Optional[bool] = None,
89
91
  ):
90
92
  if self.mr.providers.get(self.typ) is None:
@@ -110,7 +112,7 @@ class AsyncMultiRpc(BaseMultiRpc):
110
112
 
111
113
  async def multicall(
112
114
  self,
113
- block_identifier: Union[str, int] = 'latest',
115
+ block_identifier: Union[str, int] = None,
114
116
  ):
115
117
  if self.mr.providers.get(self.typ) is None:
116
118
  raise DontHaveThisRpcType(f"Doesn't have {self.typ} RPCs")
@@ -19,12 +19,13 @@ from web3.contract import Contract
19
19
  from web3.exceptions import BadResponseFormat, BlockNotFound, TimeExhausted, TransactionNotFound, Web3RPCError
20
20
  from web3.types import BlockData, BlockIdentifier, TxReceipt
21
21
 
22
- from .constants import EstimateGasLimitBuffer, GasLimit, GasUpperBound, MultiRPCLogger, ViewPolicy
22
+ from .constants import EstimateGasLimitBuffer, FlashBlockSupportedChains, GasLimit, GasUpperBound, MultiRPCLogger, \
23
+ ViewPolicy
23
24
  from .exceptions import (DontHaveThisRpcType, FailedOnAllRPCs, GetBlockFailed, NotValidViewPolicy,
24
25
  TransactionFailedStatus, TransactionValueError, Web3InterfaceException)
25
26
  from .gas_estimation import GasEstimation, GasEstimationMethod
26
27
  from .tx_trace import TxTrace
27
- from .utils import NestedDict, ResultEvent, TxPriority, get_chain_id, create_web3_from_rpc, \
28
+ from .utils import NestedDict, ResultEvent, TxPriority, create_web3_from_rpc, get_chain_id, \
28
29
  get_span_proper_label_from_provider, get_unix_time, reduce_list_of_list
29
30
 
30
31
  T = TypeVar("T")
@@ -49,7 +50,8 @@ class BaseMultiRpc(ABC):
49
50
  enable_estimate_gas_limit: bool = False,
50
51
  is_proof_authority: bool = False,
51
52
  multicall_custom_address: str = None,
52
- log_level: logging = logging.WARN
53
+ log_level: logging = logging.WARN,
54
+ is_flash_block_aware: Optional[bool] = None
53
55
  ):
54
56
  """
55
57
  Args:
@@ -90,7 +92,7 @@ class BaseMultiRpc(ABC):
90
92
  self.address = None
91
93
  self.private_key = None
92
94
  self.chain_id = None
93
-
95
+ self.is_flash_block_aware = is_flash_block_aware
94
96
  MultiRPCLogger.setLevel(log_level)
95
97
 
96
98
  def _logger_params(self, **kwargs) -> None:
@@ -99,6 +101,9 @@ class BaseMultiRpc(ABC):
99
101
  else:
100
102
  MultiRPCLogger.info(f'params={kwargs}')
101
103
 
104
+ def get_block_identifier(self, block_identifier=None):
105
+ return block_identifier or ('pending' if self.is_flash_block_aware else 'latest')
106
+
102
107
  def set_account(self, address: Union[ChecksumAddress, str], private_key: str) -> None:
103
108
  """
104
109
  Set public key and private key for sending transactions. If these values set, there is no need to pass address,
@@ -114,6 +119,11 @@ class BaseMultiRpc(ABC):
114
119
  self.providers = await create_web3_from_rpc(self.rpc_urls, self.is_proof_authority)
115
120
  self.chain_id = await get_chain_id(self.providers)
116
121
 
122
+ if self.is_flash_block_aware is None:
123
+ self.is_flash_block_aware = self.chain_id in FlashBlockSupportedChains
124
+
125
+ MultiRPCLogger.debug(f"{self.chain_id=}, {self.is_flash_block_aware=}")
126
+
117
127
  if self.gas_estimation is None and self.providers.get('transaction'):
118
128
  self.gas_estimation = GasEstimation(
119
129
  self.chain_id,
@@ -196,7 +206,7 @@ class BaseMultiRpc(ABC):
196
206
 
197
207
  async def _call_view_function(self,
198
208
  func_name: str,
199
- block_identifier: Union[str, int] = 'latest',
209
+ block_identifier: Union[str, int] = None,
200
210
  use_multicall=False,
201
211
  *args, **kwargs):
202
212
  """
@@ -222,6 +232,7 @@ class BaseMultiRpc(ABC):
222
232
  return results[max_index][2]
223
233
  return results[max_index][2][0]
224
234
 
235
+ block_identifier = self.get_block_identifier(block_identifier)
225
236
  last_error = None
226
237
  for contracts, multi_calls in zip(self.contracts['view'].values(),
227
238
  self.multi_calls['view'].values()): # type: any, List[AsyncMulticall]
@@ -246,7 +257,8 @@ class BaseMultiRpc(ABC):
246
257
  last_error = None
247
258
  for providers in providers_4_nonce.values():
248
259
  execution_list = [
249
- prov.eth.get_transaction_count(address, block_identifier=block_identifier) for prov in providers
260
+ prov.eth.get_transaction_count(address, block_identifier=self.get_block_identifier(block_identifier))
261
+ for prov in providers
250
262
  ]
251
263
  try:
252
264
  return await self.__gather_tasks(execution_list, max)
@@ -295,7 +307,7 @@ class BaseMultiRpc(ABC):
295
307
  account: LocalAccount = Account.from_key(signer_private_key)
296
308
  if enable_estimate_gas_limit:
297
309
  del tx['gas']
298
- estimate_gas = await provider.eth.estimate_gas(tx)
310
+ estimate_gas = await provider.eth.estimate_gas(tx, block_identifier=self.get_block_identifier())
299
311
  MultiRPCLogger.info(f"gas_estimation({estimate_gas} gas needed) is successful")
300
312
  return account.sign_transaction({**tx, 'gas': int(estimate_gas * EstimateGasLimitBuffer)})
301
313
  return account.sign_transaction(tx)
@@ -339,7 +351,7 @@ class BaseMultiRpc(ABC):
339
351
  raise
340
352
 
341
353
  @staticmethod
342
- def _handle_tx_trace(trace: TxTrace, func_name: str, func_args: Tuple, func_kwargs: Dict):
354
+ def _handle_tx_trace(trace: TxTrace, func_name: str, func_args: Tuple, func_kwargs: Dict) -> Exception | None:
343
355
  """
344
356
  You can override this method to customize handling failed transaction.
345
357
 
@@ -379,11 +391,12 @@ class BaseMultiRpc(ABC):
379
391
  tx_err_count += 1
380
392
  timeout *= 2
381
393
 
382
- @staticmethod
383
- async def __get_tx_trace(tx, provider_url, func_name=None, func_args=None, func_kwargs=None):
394
+ @classmethod
395
+ async def __get_tx_trace(cls, tx, provider_url, func_name=None, func_args=None, func_kwargs=None):
384
396
  tx_hash = Web3.to_hex(tx)
385
397
  trace = TxTrace(tx_hash, provider_url)
386
- BaseMultiRpc._handle_tx_trace(trace, func_name, func_args, func_kwargs)
398
+ if exception := cls._handle_tx_trace(trace, func_name, func_args, func_kwargs):
399
+ return exception
387
400
  return TransactionFailedStatus(tx_hash, func_name, func_args, func_kwargs, trace)
388
401
 
389
402
  @staticmethod
@@ -577,14 +590,15 @@ class BaseMultiRpc(ABC):
577
590
  raise
578
591
  raise last_exception
579
592
 
580
- async def get_block(self, block_identifier: BlockIdentifier = 'latest',
581
- full_transactions: bool = False) -> BlockData:
593
+ async def get_block(self, block_identifier: BlockIdentifier = None, full_transactions: bool = False) -> BlockData:
582
594
  self.check_for_view()
583
595
 
584
596
  exceptions = (HTTPError, ConnectionError, ReadTimeout, ValueError, TimeExhausted, BlockNotFound)
585
597
  last_exception = None
586
598
  for provider in self.providers['view'].values(): # type: List[AsyncWeb3]
587
- execution_tx_params_list = [p.eth.get_block(block_identifier, full_transactions) for p in provider]
599
+ execution_tx_params_list = [
600
+ p.eth.get_block(self.get_block_identifier(block_identifier), full_transactions) for p in provider
601
+ ]
588
602
  try:
589
603
  return await self.__execute_batch_tasks(
590
604
  execution_tx_params_list,
@@ -36,6 +36,10 @@ ChainIdToGas = {
36
36
  GasFromRpcChainIds = [] # for this chain ids use rpc to estimate gas
37
37
  FixedValueGas = 30
38
38
 
39
+ FlashBlockSupportedChains = [
40
+ 8453 # base
41
+ ]
42
+
39
43
  MultiRPCLoggerName = 'Multi-RPC'
40
44
  GasEstimationLoggerName = MultiRPCLoggerName + '.Gas-Estimation'
41
45
 
@@ -54,6 +54,9 @@ class MaximumRPCInEachBracketReached(Web3InterfaceException):
54
54
  class AtLastProvideOneValidRPCInEachBracket(Web3InterfaceException):
55
55
  pass
56
56
 
57
+ class AllRPCShouldSupportFlashBlockOrNot(Web3InterfaceException):
58
+ pass
59
+
57
60
 
58
61
  class TransactionValueError(Web3InterfaceException):
59
62
  pass
@@ -34,12 +34,13 @@ class MultiRpc(BaseMultiRpc):
34
34
  is_proof_authority: bool = False,
35
35
  multicall_custom_address: str = None,
36
36
  log_level: logging = logging.WARN,
37
- setup_on_init: bool = True
37
+ setup_on_init: bool = True,
38
+ is_flash_block_aware: Optional[bool] = None
38
39
  ):
39
40
  super().__init__(rpc_urls, contract_address, contract_abi, rpcs_supporting_tx_trace,
40
41
  view_policy, gas_estimation, gas_limit,
41
42
  gas_upper_bound, apm, enable_estimate_gas_limit,
42
- is_proof_authority, multicall_custom_address, log_level)
43
+ is_proof_authority, multicall_custom_address, log_level, is_flash_block_aware)
43
44
 
44
45
  for func_abi in self.contract_abi:
45
46
  if func_abi.get("stateMutability") in ("view", "pure"):
@@ -60,15 +61,15 @@ class MultiRpc(BaseMultiRpc):
60
61
  return asyncio.run(super().setup())
61
62
 
62
63
  @thread_safe
63
- def get_nonce(self, address: Union[Address, ChecksumAddress, str]) -> int:
64
- return asyncio.run(super()._get_nonce(address))
64
+ def get_nonce(self, address: Union[Address, ChecksumAddress, str], block_identifier: BlockIdentifier = None) -> int:
65
+ return asyncio.run(super()._get_nonce(address, block_identifier))
65
66
 
66
67
  @thread_safe
67
68
  def get_tx_receipt(self, tx_hash) -> TxReceipt:
68
69
  return asyncio.run(super().get_tx_receipt(tx_hash))
69
70
 
70
71
  @thread_safe
71
- def get_block(self, block_identifier: BlockIdentifier = 'latest', full_transactions: bool = False) -> BlockData:
72
+ def get_block(self, block_identifier: BlockIdentifier = None, full_transactions: bool = False) -> BlockData:
72
73
  return asyncio.run(super().get_block(block_identifier, full_transactions))
73
74
 
74
75
  @thread_safe
@@ -92,7 +93,7 @@ class MultiRpc(BaseMultiRpc):
92
93
  wait_for_receipt: int = 90,
93
94
  priority: TxPriority = TxPriority.Low,
94
95
  gas_estimation_method: GasEstimationMethod = None,
95
- block_identifier: Union[str, int] = 'latest',
96
+ block_identifier: Union[str, int] = None,
96
97
  enable_estimate_gas_limit: Optional[bool] = None,
97
98
  use_multicall=False,
98
99
  ):
@@ -100,7 +101,7 @@ class MultiRpc(BaseMultiRpc):
100
101
  raise DontHaveThisRpcType(f"Doesn't have {self.typ} RPCs")
101
102
  if self.typ == ContractFunctionType.View:
102
103
  return asyncio.run(self.mr._call_view_function(
103
- self.name, block_identifier, use_multicall, *self.args, **self.kwargs,
104
+ self.name, block_identifier, use_multicall, *self.args, **self.kwargs
104
105
  ))
105
106
  elif self.typ == ContractFunctionType.Transaction:
106
107
  return asyncio.run(self.mr._call_tx_function(
@@ -114,13 +115,13 @@ class MultiRpc(BaseMultiRpc):
114
115
  wait_for_receipt=wait_for_receipt,
115
116
  priority=priority,
116
117
  gas_estimation_method=gas_estimation_method,
117
- enable_estimate_gas_limit=enable_estimate_gas_limit
118
+ enable_estimate_gas_limit=enable_estimate_gas_limit,
118
119
  ))
119
120
 
120
121
  @thread_safe
121
122
  def multicall(
122
123
  self,
123
- block_identifier: Union[str, int] = 'latest',
124
+ block_identifier: Union[str, int] = None,
124
125
  ):
125
126
  if self.mr.providers.get(self.typ) is None:
126
127
  raise DontHaveThisRpcType(f"Doesn't have {self.typ} RPCs")
@@ -128,7 +129,7 @@ class MultiRpc(BaseMultiRpc):
128
129
  raise KwargsNotSupportedInMultiCall
129
130
  if self.typ == ContractFunctionType.View:
130
131
  return asyncio.run(self.mr._call_view_function(
131
- self.name, block_identifier, True, *self.args, **self.kwargs,
132
+ self.name, block_identifier, True, *self.args, **self.kwargs
132
133
  ))
133
134
  elif self.typ == ContractFunctionType.Transaction:
134
135
  raise TransactionTypeNotSupportedInMultiCall
@@ -17,7 +17,8 @@ from web3._utils.http_session_manager import HTTPSessionManager
17
17
  from web3.middleware import ExtraDataToPOAMiddleware
18
18
 
19
19
  from .constants import MaxRPCInEachBracket, MultiRPCLogger
20
- from .exceptions import AtLastProvideOneValidRPCInEachBracket, MaximumRPCInEachBracketReached
20
+ from .exceptions import AtLastProvideOneValidRPCInEachBracket, \
21
+ MaximumRPCInEachBracketReached
21
22
 
22
23
 
23
24
  def get_span_proper_label_from_provider(endpoint_uri):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "solver-multirpc"
3
- version = "3.1.10"
3
+ version = "3.1.12"
4
4
  description = "A robust Python library for interacting with Ethereum smart contracts via multiple RPC endpoints, ensuring reliability, availability, and load distribution with automatic retries on failure."
5
5
  authors = ["rorschach <rorschach45001@gmail.com>"]
6
6
  license = "MIT"