@@ -425,40 +425,58 @@ const INTERVAL: Duration = Duration::from_millis(500);
425425const REQUIRED_STABLE_POLLS : u32 = 4 ;
426426
427427impl IndexerClient {
428+ /// Polls sync status, yielding `(syncing, head_height)`, or `None` on a
429+ /// failed request.
430+ async fn sync_info ( & self ) -> Option < ( bool , BlockHeight ) > {
431+ let status_request = Status {
432+ is_health_check : false ,
433+ detailed : false ,
434+ } ;
435+ let Ok ( Ok ( status) ) = self
436+ . client
437+ . send_async (
438+ near_o11y:: span_wrapped_msg:: SpanWrappedMessageExt :: span_wrap ( status_request) ,
439+ )
440+ . await
441+ else {
442+ return None ;
443+ } ;
444+ Some ( (
445+ status. sync_info . syncing ,
446+ status. sync_info . latest_block_height ,
447+ ) )
448+ }
449+
450+ /// Returns once neard clears its `syncing` flag.
428451 async fn wait_for_full_sync ( & self ) {
429- let mut progress = SyncProgress :: default ( ) ;
430452 loop {
431453 tokio:: time:: sleep ( INTERVAL ) . await ;
454+ if matches ! ( self . sync_info( ) . await , Some ( ( false , _) ) ) {
455+ return ;
456+ }
457+ }
458+ }
432459
433- let status_request = Status {
434- is_health_check : false ,
435- detailed : false ,
436- } ;
437- let status_response = self
438- . client
439- . send_async (
440- near_o11y:: span_wrapped_msg:: SpanWrappedMessageExt :: span_wrap ( status_request) ,
441- )
442- . await ;
443-
444- let Ok ( Ok ( status) ) = status_response else {
445- continue ;
446- } ;
447-
448- if progress. observe (
449- status. sync_info . syncing ,
450- status. sync_info . latest_block_height ,
451- ) {
460+ /// Returns once the node is confirmed following the chain tip. Used only at
461+ /// startup: neard can report `syncing == false` while still behind (fresh
462+ /// genesis, after downtime, or while disconnected from all peers), so we
463+ /// confirm via [`SyncProgress`] before binding the streamer's cursor.
464+ async fn ensure_head_follows_tip ( & self ) {
465+ let mut progress = SyncProgress :: default ( ) ;
466+ loop {
467+ tokio:: time:: sleep ( INTERVAL ) . await ;
468+ if let Some ( ( syncing, head_height) ) = self . sync_info ( ) . await
469+ && progress. observe ( syncing, head_height)
470+ {
452471 return ;
453472 }
454473 }
455474 }
456475}
457476
458- /// Detects catch-up from head progress alone: a freshly state-syncing node
459- /// briefly reports `syncing == false` at the genesis head, and returning then
460- /// pins the streamer's `LatestSynced` cursor at that stale head. So we require a
461- /// run of non-syncing polls over which the head actually advances.
477+ /// Reports the node as caught up only after a run of non-syncing polls over
478+ /// which the head actually advances, filtering out the transient
479+ /// `syncing == false` a behind node reports before its head starts moving.
462480#[ derive( Default ) ]
463481struct SyncProgress {
464482 run_start_head : Option < BlockHeight > ,
0 commit comments