136136 wakuKademlia* : WakuKademlia
137137 ports* : BoundPorts
138138 relayReconnectFut* : Future [void ]
139+ storeCatchUpFut* : Future [void ]
140+ storeSyncRange* : timer.Duration
139141
140142 SubscriptionManager * = ref object of RootObj
141143 node* : WakuNode
@@ -379,6 +381,8 @@ proc mountStoreSync*(
379381
380382 let pubsubTopics = shards.mapIt ($ RelayShard (clusterId: cluster, shardId: it))
381383
384+ node.storeSyncRange = storeSyncRange.seconds
385+
382386 let recon = ? await SyncReconciliation .new (
383387 pubsubTopics, contentTopics, node.peerManager, node.wakuArchive,
384388 storeSyncRange.seconds, storeSyncInterval.seconds, storeSyncRelayJitter.seconds,
@@ -613,6 +617,66 @@ proc stopProvidersAndListeners*(node: WakuNode) =
613617 RequestContentTopicsHealth .clearProvider (node.brokerCtx)
614618 RequestShardTopicsHealth .clearProvider (node.brokerCtx)
615619
620+ func useSyncCatchUp * (
621+ hasReconciliation: bool ,
622+ lastOnline: Timestamp ,
623+ now: Timestamp ,
624+ syncRange: timer.Duration ,
625+ ): bool =
626+ # # Startup catch-up decision: inside the sync window neighbour
627+ # # reconciliation repairs the gap; beyond it (or on fresh start, when no
628+ # # last-online timestamp exists) store resume is needed.
629+ hasReconciliation and lastOnline > 0 and now - lastOnline <= syncRange.nanos
630+
631+ proc storeStartupCatchUp (node: WakuNode ) {.async .} =
632+ # # Store as a startup-only dependency: estimate the offline gap from the
633+ # # persisted last-online timestamp and catch up accordingly. Runs in the
634+ # # background; node startup must not block on store or sync peers.
635+ let lastOnline = node.wakuStoreResume.getLastOnlineTimestamp ().valueOr:
636+ error " catch-up: failed to read last online timestamp" , error = error
637+ Timestamp (0 )
638+
639+ let now = getNowInNanosecondTime ()
640+
641+ if useSyncCatchUp (
642+ not node.wakuStoreReconciliation.isNil (), lastOnline, now, node.storeSyncRange
643+ ):
644+ info " offline gap within sync window, catching up via reconciliation" ,
645+ gapSeconds = (now - lastOnline) div 1_000_000_000
646+ # sleep first: the switch may not be started yet and discovery may still
647+ # be warming up; retry failed attempts, the periodic sync loop remains
648+ # the safety net if none succeeds within the budget
649+ for _ in 0 ..< 24 :
650+ await sleepAsync (5 .seconds)
651+ if node.peerManager.selectPeer (WakuReconciliationCodec ).isNone ():
652+ continue
653+ (await node.wakuStoreReconciliation.storeSynchronization ()).isOkOr:
654+ error " startup reconciliation attempt failed" , error = error
655+ continue
656+ return
657+ warn " startup reconciliation did not complete; periodic sync remains the safety net"
658+ else :
659+ info " offline gap beyond sync window or fresh start, using store resume" ,
660+ gapSeconds = (now - lastOnline) div 1_000_000_000
661+ # don't burn resume tries against an empty peer store: wait (bounded)
662+ # for a store peer to be discovered first
663+ var peerWait = 24
664+ var tries = 3
665+ while tries > 0 :
666+ if node.peerManager.selectPeer (WakuStoreCodec ).isNone ():
667+ peerWait -= 1
668+ if peerWait <= 0 :
669+ warn " no store peer found for startup resume"
670+ return
671+ await sleepAsync (5 .seconds)
672+ continue
673+ (await node.wakuStoreResume.autoStoreResume ()).isOkOr:
674+ tries -= 1
675+ error " store resume failed" , triesLeft = tries, error = $ error
676+ await sleepAsync (30 .seconds)
677+ continue
678+ break
679+
616680proc start * (node: WakuNode ) {.async .} =
617681 # # Starts a created Waku Node and
618682 # # all its mounted protocols.
@@ -626,7 +690,9 @@ proc start*(node: WakuNode) {.async.} =
626690 zeroPortPresent = true
627691
628692 if not node.wakuStoreResume.isNil ():
629- await node.wakuStoreResume.start ()
693+ # catch up in the background: startup must not block on store peers
694+ node.storeCatchUpFut = node.storeStartupCatchUp ()
695+ node.wakuStoreResume.startPeriodicOnly ()
630696
631697 if not node.wakuRendezvousClient.isNil ():
632698 await node.wakuRendezvousClient.start ()
@@ -699,6 +765,12 @@ proc stop*(node: WakuNode) {.async.} =
699765 if not node.wakuArchive.isNil ():
700766 await node.wakuArchive.stopWait ()
701767
768+ if not node.storeCatchUpFut.isNil ():
769+ # in-flight store queries wrap awaits in results.catch, which can swallow
770+ # the one cancellation delivery; bound shutdown instead of waiting for
771+ # their natural completion
772+ discard await node.storeCatchUpFut.cancelAndWait ().withTimeout (5 .seconds)
773+
702774 if not node.wakuStoreResume.isNil ():
703775 await node.wakuStoreResume.stopWait ()
704776
0 commit comments