@@ -130,17 +130,15 @@ where
130130 for sub in subs {
131131 let ( tx, rx) = mpsc:: channel :: < TaggedChainLog > ( RECONNECT_CHANNEL_BUF ) ;
132132 let pool = pool. clone ( ) ;
133- // The cursor key is constant per subscription and cloned onto every
134- // log; `Arc` keeps that clone cheap.
135- let cursor_key: Option < Arc < str > > = sub. cursor_key . map ( Arc :: from) ;
133+ let resume = ChainLogResume {
134+ // The cursor key is constant per subscription and cloned onto every
135+ // log; `Arc` keeps that clone cheap.
136+ cursor_key : sub. cursor_key . map ( Arc :: from) ,
137+ initial_cursor : sub. initial_cursor ,
138+ max_lookback : sub. max_lookback ,
139+ } ;
136140 tasks. push ( executor. spawn ( Box :: pin ( reconnecting_chain_log_task (
137- pool,
138- sub. module ,
139- sub. chain ,
140- sub. filter ,
141- cursor_key,
142- sub. initial_cursor ,
143- tx,
141+ pool, sub. module , sub. chain , sub. filter , resume, tx,
144142 ) ) ) ) ;
145143 let tagged: TaggedChainLogStream = Box :: pin ( receiver_stream ( rx) ) ;
146144 streams. push ( tagged) ;
@@ -245,6 +243,18 @@ where
245243 }
246244}
247245
246+ /// Per-subscription resume and backfill knobs for a chain-log task.
247+ struct ChainLogResume {
248+ /// Durable cursor key, `Some` for a `resume` subscription; the block
249+ /// under it seeds `initial_cursor`.
250+ cursor_key : Option < Arc < str > > ,
251+ /// Persisted resume block read at boot; the first open starts here.
252+ initial_cursor : Option < u64 > ,
253+ /// Opt-in cap (in blocks) on how far back the poller backfills; `None`
254+ /// backfills the whole gap.
255+ max_lookback : Option < u64 > ,
256+ }
257+
248258/// Poller-backed loop for a single (module, chain) chain-log
249259/// subscription. Instead of `eth_subscribe(logs)` - which silently
250260/// drops events emitted during a WebSocket reconnect - it drives
@@ -254,19 +264,25 @@ where
254264/// transport's own retries), or a reorg deeper than the poller's
255265/// retained history, ends the poller stream; this loop then re-opens
256266/// from the block after the last one it delivered and backfills the
257- /// entire missed range (nothing skipped) with exponential backoff.
267+ /// entire missed range (nothing skipped) with exponential backoff -
268+ /// unless the subscription set `max_lookback`, which bounds how far back
269+ /// the backfill reaches.
258270async fn reconnecting_chain_log_task < C > (
259271 pool : C ,
260272 module : String ,
261273 chain : Chain ,
262274 filter : alloy_rpc_types_eth:: Filter ,
263- cursor_key : Option < Arc < str > > ,
264- initial_cursor : Option < u64 > ,
275+ resume : ChainLogResume ,
265276 tx : mpsc:: Sender < TaggedChainLog > ,
266277) -> TaskExit
267278where
268279 C : ChainProvider + Send + Sync + ' static ,
269280{
281+ let ChainLogResume {
282+ cursor_key,
283+ initial_cursor,
284+ max_lookback,
285+ } = resume;
270286 let chain_id = chain. id ( ) ;
271287 let mut attempt: u32 = 0 ;
272288 let mut last_event: Option < Instant > = None ;
@@ -304,11 +320,28 @@ where
304320 // - otherwise a re-open resumes just after the last delivered block
305321 // (within-process gap-free), or at head on the very first open.
306322 // Either way the whole gap is backfilled with no lower floor, so
307- // nothing is ever skipped.
308- let start_block = match boot_resume. take ( ) {
323+ // nothing is skipped unless the subscription set `max_lookback` .
324+ let mut start_block = match boot_resume. take ( ) {
309325 Some ( resume) => resume. min ( head) ,
310326 None => poller_resume_block ( last_seen_block, head) ,
311327 } ;
328+ // Opt-in bound: `max_lookback` caps how far back a resume
329+ // subscription backfills. The default (`None`) backfills fully; a
330+ // set cap clamps the start up to `head - cap` and surfaces the
331+ // dropped oldest blocks.
332+ if let Some ( cap) = max_lookback {
333+ let floor = head. saturating_sub ( cap) ;
334+ if start_block < floor {
335+ warn ! (
336+ module = %module,
337+ chain_id,
338+ skipped_from = start_block,
339+ skipped_to = floor,
340+ "chain-log gap exceeds max_lookback - skipping the oldest missed blocks" ,
341+ ) ;
342+ start_block = floor;
343+ }
344+ }
312345 // A large gap is backfilled in full (never skipped); surface it so a long
313346 // catch-up is visible rather than looking like a stall.
314347 if head. saturating_sub ( start_block) >= LARGE_GAP_LOG_THRESHOLD {
0 commit comments