@@ -125,17 +125,18 @@ def get_logs(
125
125
p_bar = None ,
126
126
no_retry : bool = False
127
127
) -> list [LogReceipt ]:
128
- if not self .w3 .should_retry :
129
- no_retry = True
128
+ filter_block_range = self .w3 .filter_block_range
129
+ if filter_block_range == 0 :
130
+ raise Exception ("RPC does not support eth_getLogs" )
130
131
131
- # getting the respective block numbers, could be block hashes or strings like "latest"
132
- from_block = filter_params ["fromBlock" ]
133
- to_block = filter_params ["toBlock" ]
134
- if not isinstance (from_block , int ):
135
- from_block = self .get_block (from_block )["number" ]
136
- if not isinstance (to_block , int ):
137
- to_block = self .get_block (to_block )["number" ]
132
+ # getting logs for a single block defined by its block hash. No drama
133
+ if "blockHash" in filter_params :
134
+ assert "fromBlock" not in filter_params and "toBlock" not in filter_params
135
+ return self .get_logs_inner (filter_params , no_retry = no_retry )
138
136
137
+ # sanitizing block numbers, could be strings like "latest"
138
+ filter_params ["fromBlock" ] = from_block = self .get_block_number_from_identifier (filter_params ["fromBlock" ])
139
+ filter_params ["toBlock" ] = to_block = self .get_block_number_from_identifier (filter_params ["toBlock" ])
139
140
assert to_block >= from_block , f"{ from_block = } , { to_block = } "
140
141
141
142
# note: fromBlock and toBlock are both inclusive. e.g. 5 to 6 are 2 blocks
@@ -147,51 +148,66 @@ def get_logs(
147
148
from tqdm import tqdm
148
149
p_bar = tqdm (total = num_blocks )
149
150
151
+ kwargs = dict (
152
+ show_progress_bar = show_progress_bar ,
153
+ p_bar = p_bar ,
154
+ no_retry = no_retry ,
155
+ )
156
+
157
+ # the latest blocks might be available on some nodes but not at others.
158
+ # setting toBlock to a block bigger than the latest known block of the node
159
+ # simply ignores logs from the missing block
160
+ # to prevent this, we get the latest blocks individually by their hashes
161
+ unstable_blocks = self .w3 .unstable_blocks
162
+ if to_block > self .w3 .latest_seen_block - unstable_blocks and to_block > self .block_number - unstable_blocks :
163
+ results = []
164
+ while to_block > self .w3 .latest_seen_block - unstable_blocks and to_block >= from_block :
165
+ single_hash_filter = {** filter_params , "blockHash" : self .get_block (to_block )["hash" ]}
166
+ del single_hash_filter ["fromBlock" ]
167
+ del single_hash_filter ["toBlock" ]
168
+ results += self .get_logs_inner (single_hash_filter , no_retry = no_retry )
169
+ to_block -= 1
170
+ if p_bar is not None :
171
+ p_bar .update (1 )
172
+ if to_block >= from_block :
173
+ results += self .get_logs ({** filter_params , "toBlock" : to_block }, ** kwargs )
174
+ return results
175
+
176
+ # getting logs for a single block, which is not at the chain head. No drama
177
+ if num_blocks == 1 :
178
+ return self .get_logs_inner (filter_params , no_retry = no_retry )
179
+
150
180
# if we already know that the filter range is too large, split it
151
- filter_block_range = self .w3 .filter_block_range
152
- if filter_block_range == 0 :
153
- raise Exception ("RPC does not support eth_getLogs" )
154
181
if num_blocks > filter_block_range :
155
182
results = []
156
183
for filter_start in range (from_block , to_block + 1 , filter_block_range ):
157
- filter_end = min ( filter_start + filter_block_range - 1 , to_block )
158
- partial_filter = filter_params . copy ()
159
- partial_filter [ "fromBlock" ] = filter_start
160
- partial_filter [ "toBlock" ] = filter_end
161
- results += self . get_logs ( partial_filter , show_progress_bar = show_progress_bar , p_bar = p_bar )
184
+ results += self . get_logs ({
185
+ ** filter_params ,
186
+ "fromBlock" : filter_start ,
187
+ "toBlock" : min ( filter_start + filter_block_range - 1 , to_block ),
188
+ }, ** kwargs )
162
189
return results
163
190
164
191
# get logs
165
192
try :
166
193
events = self ._get_logs (filter_params )
167
- except Exception :
168
- # if errors should not be retried, still do splitting but not retry if it can not be split further
169
- if no_retry and num_blocks == 1 :
170
- raise
171
- else :
172
194
if p_bar is not None :
173
195
p_bar .update (num_blocks )
174
196
return events
175
-
176
- # if directly getting logs did not work, split the filter range and try again
177
- if num_blocks > 1 :
197
+ except Exception :
198
+ # split the filter range and try again
178
199
mid_block = (from_block + to_block ) // 2
179
- left_filter = filter_params .copy ()
180
- left_filter ["toBlock" ] = mid_block
181
- right_filter = filter_params .copy ()
182
- right_filter ["fromBlock" ] = mid_block + 1
200
+ left_filter = {** filter_params , "toBlock" : mid_block }
201
+ right_filter = {** filter_params , "fromBlock" : mid_block + 1 }
202
+ return self .get_logs (left_filter , ** kwargs ) + self .get_logs (right_filter , ** kwargs )
183
203
184
- results = []
185
- results += self .get_logs ( left_filter , show_progress_bar = show_progress_bar , p_bar = p_bar )
186
- results += self . get_logs ( right_filter , show_progress_bar = show_progress_bar , p_bar = p_bar )
187
- return results
204
+ def get_logs_inner ( self , filter_params : FilterParams , no_retry : bool = False ):
205
+ if not self .w3 . should_retry :
206
+ no_retry = True
207
+ return exponential_retry ( func_name = "get_logs" )( self . _get_logs )( filter_params , no_retry = no_retry )
188
208
189
- # filter is trying to get a single block, retrying till it works
190
- assert from_block == to_block and num_blocks == 1 , f"{ from_block = } , { to_block = } , { num_blocks = } "
191
- events = exponential_retry (func_name = "get_logs" )(self ._get_logs )(filter_params )
192
- if p_bar is not None :
193
- p_bar .update (num_blocks )
194
- return events
209
+ def get_block_number_from_identifier (self , block_identifier : BlockIdentifier ) -> BlockNumber :
210
+ return block_identifier if isinstance (block_identifier , int ) else self .get_block (block_identifier )["number" ]
195
211
196
212
def _chain_id (self ):
197
213
# usually this causes an RPC call and is used in every eth_call. Getting it once in the init and then not again.
0 commit comments