1- use crate :: { RpcBlock , RpcChainSegment , RpcHostError } ;
1+ use crate :: { RpcBlock , RpcChainSegment , RpcHostError , latest :: Latest } ;
22use alloy:: {
33 consensus:: { BlockHeader , transaction:: Recovered } ,
44 eips:: { BlockId , BlockNumberOrTag } ,
@@ -8,7 +8,7 @@ use alloy::{
88 pubsub:: SubscriptionStream ,
99 rpc:: types:: Header as RpcHeader ,
1010} ;
11- use futures_util:: { StreamExt , stream:: FuturesOrdered } ;
11+ use futures_util:: { StreamExt , TryStreamExt , stream} ;
1212use signet_node_types:: { HostNotification , HostNotificationKind , HostNotifier , RevertRange } ;
1313use signet_types:: primitives:: { RecoveredBlock , SealedBlock , TransactionSigned } ;
1414use std:: { collections:: VecDeque , sync:: Arc , time:: Instant } ;
@@ -56,7 +56,8 @@ pub struct RpcHostNotifier<P> {
5656 provider : P ,
5757
5858 /// Subscription stream of new block headers (used as wake-up signal).
59- header_sub : SubscriptionStream < RpcHeader > ,
59+ /// Wrapped in [`Latest`] to coalesce stale buffered headers.
60+ header_sub : Latest < SubscriptionStream < RpcHeader > > ,
6061
6162 /// Local chain view — lightweight ring buffer of (number, hash).
6263 chain_view : VecDeque < ( u64 , B256 ) > ,
@@ -80,6 +81,9 @@ pub struct RpcHostNotifier<P> {
8081 /// Max blocks per backfill batch.
8182 backfill_batch_size : u64 ,
8283
84+ /// Maximum number of concurrent RPC block fetches.
85+ max_rpc_concurrency : usize ,
86+
8387 /// Seconds per slot, used for epoch calculation.
8488 slot_seconds : u64 ,
8589
@@ -92,6 +96,7 @@ impl<P> core::fmt::Debug for RpcHostNotifier<P> {
9296 f. debug_struct ( "RpcHostNotifier" )
9397 . field ( "chain_view_len" , & self . chain_view . len ( ) )
9498 . field ( "buffer_capacity" , & self . buffer_capacity )
99+ . field ( "max_rpc_concurrency" , & self . max_rpc_concurrency )
95100 . field ( "backfill_from" , & self . backfill_from )
96101 . finish_non_exhaustive ( )
97102 }
@@ -107,19 +112,21 @@ where
107112 header_sub : SubscriptionStream < RpcHeader > ,
108113 buffer_capacity : usize ,
109114 backfill_batch_size : u64 ,
115+ max_rpc_concurrency : usize ,
110116 slot_seconds : u64 ,
111117 genesis_timestamp : u64 ,
112118 ) -> Self {
113119 Self {
114120 provider,
115- header_sub,
121+ header_sub : Latest :: new ( header_sub ) ,
116122 chain_view : VecDeque :: with_capacity ( buffer_capacity) ,
117123 buffer_capacity,
118124 cached_safe : None ,
119125 cached_finalized : None ,
120126 last_tag_epoch : None ,
121127 backfill_from : None ,
122128 backfill_batch_size,
129+ max_rpc_concurrency,
123130 slot_seconds,
124131 genesis_timestamp,
125132 }
@@ -213,22 +220,17 @@ where
213220 /// Fetch full blocks+receipts for a list of hashes, concurrently.
214221 ///
215222 /// Hashes must be in ascending block-number order. Results preserve
216- /// that order.
223+ /// that order. Concurrency is bounded by [`Self::max_rpc_concurrency`].
217224 #[ tracing:: instrument( level = "debug" , skip_all, fields( count = hashes. len( ) ) ) ]
218225 async fn fetch_blocks_by_hash (
219226 & self ,
220227 hashes : & [ ( u64 , B256 ) ] ,
221228 ) -> Result < Vec < Arc < RpcBlock > > , RpcHostError > {
222- let mut futures = hashes
223- . iter ( )
224- . map ( |& ( _, hash) | self . fetch_block_by_hash ( hash) )
225- . collect :: < FuturesOrdered < _ > > ( ) ;
226-
227- let mut blocks = Vec :: with_capacity ( hashes. len ( ) ) ;
228- while let Some ( result) = futures. next ( ) . await {
229- blocks. push ( Arc :: new ( result?) ) ;
230- }
231- Ok ( blocks)
229+ stream:: iter ( hashes. iter ( ) . copied ( ) . map ( |( _, hash) | self . fetch_block_by_hash ( hash) ) )
230+ . buffered ( self . max_rpc_concurrency )
231+ . map_ok ( Arc :: new)
232+ . try_collect ( )
233+ . await
232234 }
233235
234236 /// Fetch a single block with receipts by number (used for backfill only).
@@ -250,22 +252,19 @@ where
250252
251253 /// Fetch a range of blocks by number concurrently (used for backfill only).
252254 ///
253- /// Returns an empty `Vec` if `from > to`.
255+ /// Returns an empty `Vec` if `from > to`. Concurrency is bounded by
256+ /// [`Self::max_rpc_concurrency`].
254257 #[ tracing:: instrument( level = "debug" , skip_all, fields( from, to) ) ]
255258 async fn fetch_range ( & self , from : u64 , to : u64 ) -> Result < Vec < Arc < RpcBlock > > , RpcHostError > {
256259 if from > to {
257260 return Ok ( Vec :: new ( ) ) ;
258261 }
259262
260- let mut futures = ( from..=to)
261- . map ( |number| self . fetch_block_by_number ( number) )
262- . collect :: < FuturesOrdered < _ > > ( ) ;
263-
264- let mut blocks = Vec :: with_capacity ( ( to - from + 1 ) as usize ) ;
265- while let Some ( result) = futures. next ( ) . await {
266- blocks. push ( Arc :: new ( result?) ) ;
267- }
268- Ok ( blocks)
263+ stream:: iter ( ( from..=to) . map ( |number| self . fetch_block_by_number ( number) ) )
264+ . buffered ( self . max_rpc_concurrency )
265+ . map_ok ( Arc :: new)
266+ . try_collect ( )
267+ . await
269268 }
270269
271270 // ── Epoch / tag helpers ────────────────────────────────────────
0 commit comments