Skip to content

Commit cac5a74

Browse files
committed
w3.eth.call now accepts no_retry in transaction data. Web3Advanced now checks max filter_block_range and revert_reason_available
1 parent e422e4f commit cac5a74

File tree

5 files changed

+103
-44
lines changed

5 files changed

+103
-44
lines changed

IceCreamSwapWeb3/EthAdvanced.py

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from time import sleep
2+
from typing import Optional
23

34
from web3.eth import Eth
45
from web3.exceptions import ContractLogicError
5-
from web3.types import FilterParams, LogReceipt
6+
from web3.types import FilterParams, LogReceipt, CallOverride, BlockIdentifier, TxParams
7+
8+
from IceCreamSwapWeb3 import Web3Advanced
69

710

811
def exponential_retry(func_name: str = None):
912
def wrapper(func):
10-
def inner(*args, no_retry=False, **kwargs):
13+
def inner(*args, no_retry: bool = False, **kwargs):
1114
if no_retry:
1215
return func(*args, **kwargs)
1316

@@ -33,9 +36,10 @@ def inner(*args, no_retry=False, **kwargs):
3336

3437

3538
class EthAdvanced(Eth):
36-
# todo: implement multicall
39+
w3: Web3Advanced
40+
3741
METHODS_TO_RETRY = [
38-
'fee_history', 'call', 'create_access_list', 'estimate_gas',
42+
'fee_history', 'create_access_list', 'estimate_gas',
3943
'get_transaction', 'get_raw_transaction', 'get_raw_transaction_by_block',
4044
'send_transaction', 'send_raw_transaction', 'get_block', 'get_balance',
4145
'get_code', 'get_transaction_count', 'get_transaction_receipt',
@@ -50,23 +54,6 @@ class EthAdvanced(Eth):
5054
'max_priority_fee', 'mining', 'syncing'
5155
]
5256

53-
FILTER_RANGES_TO_TRY = sorted([
54-
10_000,
55-
5_000,
56-
2_000,
57-
1_000,
58-
500,
59-
200,
60-
100,
61-
50,
62-
20,
63-
10,
64-
5,
65-
2,
66-
1
67-
], reverse=True)
68-
assert FILTER_RANGES_TO_TRY[-1] == 1
69-
7057
def __init__(self, w3):
7158
super().__init__(w3=w3)
7259

@@ -75,8 +62,6 @@ def __init__(self, w3):
7562

7663
self.chain_id_cached = super()._chain_id()
7764

78-
self.filter_block_range = self._find_max_filter_range()
79-
8065
def _wrap_methods_with_retry(self):
8166
for method_name in self.METHODS_TO_RETRY:
8267
method = getattr(self, method_name)
@@ -87,6 +72,26 @@ def _wrap_methods_with_retry(self):
8772
wrapped_prop = property(exponential_retry(func_name=prop_name)(prop.fget))
8873
setattr(self.__class__, prop_name, wrapped_prop)
8974

75+
def call(
76+
self,
77+
transaction: TxParams,
78+
block_identifier: Optional[BlockIdentifier] = None,
79+
state_override: Optional[CallOverride] = None,
80+
ccip_read_enabled: Optional[bool] = None,
81+
no_retry: bool = None,
82+
):
83+
if "no_retry" in transaction:
84+
no_retry = transaction["no_retry"]
85+
del transaction["no_retry"]
86+
87+
return exponential_retry(func_name="call")(super().call)(
88+
transaction=transaction,
89+
block_identifier=block_identifier,
90+
state_override=state_override,
91+
ccip_read_enabled=ccip_read_enabled,
92+
no_retry=no_retry,
93+
)
94+
9095
def get_logs(self, filter_params: FilterParams, show_progress_bar=False, p_bar=None) -> list[LogReceipt]:
9196
# getting the respective block numbers, could be block hashes or strings like "latest"
9297
from_block = filter_params["fromBlock"]
@@ -108,10 +113,13 @@ def get_logs(self, filter_params: FilterParams, show_progress_bar=False, p_bar=N
108113
p_bar = tqdm(total=num_blocks)
109114

110115
# if we already know that the filter range is too large, split it
111-
if num_blocks > self.filter_block_range:
116+
filter_block_range = self.w3.filter_block_range
117+
if filter_block_range == 0:
118+
raise Exception("RPC does not support eth_getLogs")
119+
if num_blocks > filter_block_range:
112120
results = []
113-
for filter_start in range(from_block, to_block + 1, self.filter_block_range):
114-
filter_end = min(filter_start + self.filter_block_range - 1, to_block)
121+
for filter_start in range(from_block, to_block + 1, filter_block_range):
122+
filter_end = min(filter_start + filter_block_range - 1, to_block)
115123
partial_filter = filter_params.copy()
116124
partial_filter["fromBlock"] = filter_start
117125
partial_filter["toBlock"] = filter_end
@@ -150,23 +158,6 @@ def get_logs(self, filter_params: FilterParams, show_progress_bar=False, p_bar=N
150158
p_bar.update(num_blocks)
151159
return events
152160

153-
def _find_max_filter_range(self):
154-
current_block = self.block_number
155-
for filter_range in self.FILTER_RANGES_TO_TRY:
156-
try:
157-
# getting logs from the 0 address as it does not emit any logs.
158-
# This way we can test the maximum allowed filter range without getting back a ton of logs
159-
result = self._get_logs({
160-
"address": "0x0000000000000000000000000000000000000000",
161-
"fromBlock": current_block - 5 - filter_range + 1,
162-
"toBlock": current_block - 5,
163-
})
164-
assert result == []
165-
return filter_range
166-
except Exception:
167-
pass
168-
raise ValueError("Unable to use eth_getLogs")
169-
170161
def _chain_id(self):
171162
# usually this causes an RPC call and is used in every eth_call. Getting it once in the init and then not again.
172163
return self.chain_id_cached

IceCreamSwapWeb3/Web3Advanced.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from web3 import Web3
2+
from web3.exceptions import ContractLogicError
23
from web3.main import get_default_modules
34
from web3.middleware import geth_poa_middleware
45

@@ -8,14 +9,32 @@
89
class Web3Advanced(Web3):
910
eth: EthAdvanced
1011

12+
FILTER_RANGES_TO_TRY = sorted([
13+
10_000,
14+
5_000,
15+
2_000,
16+
1_000,
17+
500,
18+
200,
19+
100,
20+
50,
21+
20,
22+
10,
23+
5,
24+
2,
25+
1
26+
], reverse=True)
27+
assert FILTER_RANGES_TO_TRY[-1] == 1
28+
1129
def __init__(
1230
self,
1331
node_url: str,
1432
should_retry=True,
1533
):
34+
self.node_url = node_url
1635
self.should_retry = should_retry
1736

18-
provider = self._construct_provider(node_url=node_url)
37+
provider = self._construct_provider(node_url=self.node_url)
1938

2039
# use the EthAdvanced class instead of the Eth class for w3.eth
2140
modules = get_default_modules()
@@ -25,6 +44,11 @@ def __init__(
2544

2645
self.middleware_onion.inject(geth_poa_middleware, layer=0, name="poa") # required for pos chains
2746

47+
self.filter_block_range = self._find_max_filter_range()
48+
self.revert_reason_available: bool = self._check_revert_reason_available()
49+
if not self.revert_reason_available:
50+
print(f"RPC {self.node_url} does not return revert reasons")
51+
2852
@staticmethod
2953
def _construct_provider(node_url):
3054
assert "://" in node_url
@@ -35,3 +59,38 @@ def _construct_provider(node_url):
3559
return Web3.WebsocketProvider(node_url)
3660
else:
3761
raise ValueError(f"Unknown protocol for RPC URL {node_url}")
62+
63+
def _find_max_filter_range(self):
64+
current_block = self.eth.block_number
65+
for filter_range in self.FILTER_RANGES_TO_TRY:
66+
try:
67+
# getting logs from the 0 address as it does not emit any logs.
68+
# This way we can test the maximum allowed filter range without getting back a ton of logs
69+
result = self.eth._get_logs({
70+
"address": "0x0000000000000000000000000000000000000000",
71+
"fromBlock": current_block - 5 - filter_range + 1,
72+
"toBlock": current_block - 5,
73+
})
74+
assert result == []
75+
return filter_range
76+
except Exception:
77+
pass
78+
print(f"Can not use eth_getLogs with RPC {self.node_url}")
79+
return 0
80+
81+
def _check_revert_reason_available(self):
82+
with open("abi/RevertTester.abi") as f:
83+
revert_tester_abi = f.read()
84+
with open("bytecode/RevertTester.bytecode") as f:
85+
revert_tester_bytecode = f.read()
86+
revert_tester_contract = self.eth.contract(abi=revert_tester_abi, bytecode=revert_tester_bytecode)
87+
try:
88+
self.eth.call({
89+
"data": revert_tester_contract.constructor().data_in_transaction
90+
}, no_retry=True)
91+
# should revert, if not, reverts are useless
92+
return False
93+
except Exception as e:
94+
if not isinstance(e, ContractLogicError):
95+
return False
96+
return e.message == "execution reverted: abc"

IceCreamSwapWeb3/abi/RevertTester.abi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"inputs": [],
4+
"stateMutability": "nonpayable",
5+
"type": "constructor"
6+
}
7+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6080604052348015600f57600080fd5b5060405162461bcd60e51b815260206004820152600360248201526261626360e81b604482015260640160405180910390fdfe

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
requirements = [
88
'web3',
9+
'rlp',
910
]
1011

1112
# Setting up

0 commit comments

Comments
 (0)