@@ -109,6 +109,19 @@ fn should_halve_range(err: &str, chunk_start: u64, chunk_end: u64) -> bool {
109109 is_rpc_range_error ( err) && ( chunk_end - chunk_start) > 1000
110110}
111111
112+ fn split_log_query_range ( chunk_start : u64 , chunk_end : u64 ) -> Option < ( ( u64 , u64 ) , ( u64 , u64 ) ) > {
113+ if chunk_end <= chunk_start {
114+ return None ;
115+ }
116+
117+ let lower_end = chunk_start + ( ( chunk_end - chunk_start) / 2 ) ;
118+ if lower_end >= chunk_end {
119+ return None ;
120+ }
121+
122+ Some ( ( ( chunk_start, lower_end) , ( lower_end + 1 , chunk_end) ) )
123+ }
124+
112125async fn append_discovered_channels_from_logs (
113126 logs : & [ alloy:: rpc:: types:: Log ] ,
114127 provider : & alloy:: providers:: RootProvider < alloy:: network:: Ethereum > ,
@@ -264,41 +277,80 @@ pub async fn find_all_channels_for_payer(
264277 "scanning blocks for ChannelOpened events"
265278 ) ;
266279
280+ let mut pending_ranges = Vec :: new ( ) ;
267281 let mut chunk_end = latest;
268- while chunk_end > earliest {
269- let chunk_start = chunk_end
270- . saturating_sub ( LOG_QUERY_BLOCK_RANGE )
271- . max ( earliest) ;
282+ while chunk_end > earliest || !pending_ranges. is_empty ( ) {
283+ let ( ( chunk_start, query_end) , from_pending_range) = match pending_ranges. pop ( ) {
284+ Some ( range) => ( range, true ) ,
285+ None => (
286+ (
287+ chunk_end
288+ . saturating_sub ( LOG_QUERY_BLOCK_RANGE )
289+ . max ( earliest) ,
290+ chunk_end,
291+ ) ,
292+ false ,
293+ ) ,
294+ } ;
272295
273296 let filter = Filter :: new ( )
274297 . address ( escrow)
275298 . event_signature ( event_topic)
276299 . topic2 ( payer_topic)
277300 . from_block ( BlockNumberOrTag :: Number ( chunk_start) )
278- . to_block ( BlockNumberOrTag :: Number ( chunk_end ) ) ;
301+ . to_block ( BlockNumberOrTag :: Number ( query_end ) ) ;
279302
280303 let logs = match provider. get_logs ( & filter) . await {
281304 Ok ( logs) => logs,
282305 Err ( e) => {
283306 let err_str = e. to_string ( ) ;
284- if should_halve_range ( & err_str, chunk_start, chunk_end) {
285- let halved = ( chunk_end - chunk_start) / 2 ;
307+ if should_halve_range ( & err_str, chunk_start, query_end) {
308+ let Some ( ( lower, upper) ) = split_log_query_range ( chunk_start, query_end) else {
309+ tracing:: warn!(
310+ network = network. as_str( ) ,
311+ chunk_start,
312+ chunk_end = query_end,
313+ %e,
314+ "failed to split oversized log query range"
315+ ) ;
316+ if !from_pending_range {
317+ if chunk_start == earliest {
318+ break ;
319+ }
320+ chunk_end = chunk_start. saturating_sub ( 1 ) ;
321+ }
322+ continue ;
323+ } ;
286324 tracing:: debug!(
287325 network = network. as_str( ) ,
288- old_range = chunk_end - chunk_start,
289- new_range = halved,
326+ old_range = query_end - chunk_start,
327+ lower_start = lower. 0 ,
328+ lower_end = lower. 1 ,
329+ upper_start = upper. 0 ,
330+ upper_end = upper. 1 ,
290331 "RPC range too large, halving"
291332 ) ;
292- chunk_end = chunk_start + halved;
333+ pending_ranges. push ( upper) ;
334+ pending_ranges. push ( lower) ;
335+ if !from_pending_range {
336+ if chunk_start == earliest {
337+ chunk_end = earliest;
338+ } else {
339+ chunk_end = chunk_start. saturating_sub ( 1 ) ;
340+ }
341+ }
293342 continue ;
294343 }
295344 tracing:: warn!(
296345 network = network. as_str( ) ,
297346 chunk_start,
298- chunk_end,
347+ chunk_end = query_end ,
299348 %e,
300349 "failed to query logs, skipping block range"
301350 ) ;
351+ if from_pending_range {
352+ continue ;
353+ }
302354 if chunk_start == earliest {
303355 break ;
304356 }
@@ -309,6 +361,9 @@ pub async fn find_all_channels_for_payer(
309361
310362 append_discovered_channels_from_logs ( & logs, & provider, network, escrow, & mut results) . await ;
311363
364+ if from_pending_range {
365+ continue ;
366+ }
312367 if chunk_start == earliest {
313368 break ;
314369 }
@@ -389,3 +444,30 @@ pub async fn query_token_balance(
389444 } ) ?;
390445 Ok ( balance)
391446}
447+
448+ #[ cfg( test) ]
449+ mod tests {
450+ use super :: * ;
451+
452+ #[ test]
453+ fn split_log_query_range_covers_both_halves_without_gap ( ) {
454+ let ( lower, upper) = split_log_query_range ( 80 , 100 ) . expect ( "range should split" ) ;
455+
456+ assert_eq ! ( lower, ( 80 , 90 ) ) ;
457+ assert_eq ! ( upper, ( 91 , 100 ) ) ;
458+ }
459+
460+ #[ test]
461+ fn split_log_query_range_preserves_single_block_boundary ( ) {
462+ let ( lower, upper) = split_log_query_range ( 0 , 1 ) . expect ( "two block range should split" ) ;
463+
464+ assert_eq ! ( lower, ( 0 , 0 ) ) ;
465+ assert_eq ! ( upper, ( 1 , 1 ) ) ;
466+ }
467+
468+ #[ test]
469+ fn split_log_query_range_rejects_empty_range ( ) {
470+ assert_eq ! ( split_log_query_range( 42 , 42 ) , None ) ;
471+ assert_eq ! ( split_log_query_range( 43 , 42 ) , None ) ;
472+ }
473+ }
0 commit comments