@@ -124,18 +124,14 @@ func (s *Syncer) Start(ctx context.Context) error {
124124 s .p2pHandler = NewP2PHandler (s .headerStore .Store (), s .dataStore .Store (), s .cache , s .genesis , s .logger )
125125
126126 // Start main processing loop
127- s .wg .Add (1 )
128- go func () {
129- defer s .wg .Done ()
127+ s .wg .Go (func () {
130128 s .processLoop ()
131- }( )
129+ })
132130
133- // Start sync loop (DA and P2P retrieval)
134- s .wg .Add (1 )
135- go func () {
136- defer s .wg .Done ()
131+ // Start P2p + DA syncing loop
132+ s .wg .Go (func () {
137133 s .syncLoop ()
138- }( )
134+ })
139135
140136 s .logger .Info ().Msg ("syncer started" )
141137 return nil
@@ -244,11 +240,10 @@ func (s *Syncer) processLoop() {
244240 }
245241}
246242
247- // syncLoop handles synchronization from DA and P2P sources.
243+ // syncLoop handles the node's initial sync process from both
244+ // DA and P2P sources and then spawns tip-tracking routines
245+ // upon successful initial sync.
248246func (s * Syncer ) syncLoop () {
249- s .logger .Info ().Msg ("starting sync loop" )
250- defer s .logger .Info ().Msg ("sync loop stopped" )
251-
252247 if delay := time .Until (s .genesis .StartTime ); delay > 0 {
253248 s .logger .Info ().Dur ("delay" , delay ).Msg ("waiting until genesis to start syncing" )
254249 select {
@@ -258,25 +253,104 @@ func (s *Syncer) syncLoop() {
258253 }
259254 }
260255
261- // Backoff control when DA replies with errors
262- nextDARequestAt := & time. Time {}
256+ s . logger . Info (). Msg ( "starting sync loop" )
257+ defer s . logger . Info (). Msg ( "sync loop stopped" )
263258
264- for {
265- select {
266- case <- s .ctx .Done ():
259+ // first flush any existing pending events
260+ s .processPendingEvents ()
261+
262+ // get starting height of node
263+ ctx , cancel := context .WithTimeout (s .ctx , time .Second ) // TODO @tac0turtle: you can adjust timeouts to realistic
264+ startHeight , err := s .store .Height (ctx )
265+ cancel ()
266+ if err != nil {
267+ // TODO @tac0turtle: this would be fatal
268+ s .logger .Error ().Err (err ).Msg ("failed to get start height for sync loop" )
269+ return
270+ }
271+
272+ wg := sync.WaitGroup {}
273+ // check if p2p stores are behind the node's store. Note that processing
274+ // header range requires the header to be available in the p2p store in
275+ // order to proceed (regardless of height of data p2p store, so we only
276+ // trigger processing for p2p header store
277+ if s .headerStore .Store ().Height () < startHeight {
278+ // trigger sync job async
279+ wg .Go (func () {
280+ s .p2pHandler .ProcessHeaderRange (s .ctx , s .headerStore .Store ().Height (), startHeight + 1 , s .heightInCh )
281+ })
282+ }
283+
284+ // check if DA is behind the node's store, and if so, sync.
285+ if s .GetDAHeight () < startHeight {
286+ // trigger sync job from DA async
287+ wg .Go (func () {
288+ s .syncDARange (startHeight )
289+ })
290+ }
291+ wg .Wait ()
292+
293+ // begin tip-tracking routines
294+ s .wg .Go (func () {
295+ s .waitForNewP2PHeights ()
296+ })
297+ s .wg .Go (func () {
298+ s .daRetrievalLoop ()
299+ })
300+ }
301+
302+ func (s * Syncer ) syncDARange (toHeight uint64 ) {
303+ currentDAHeight := s .GetDAHeight ()
304+
305+ for currentDAHeight <= toHeight {
306+ events , err := s .daRetriever .RetrieveFromDA (s .ctx , currentDAHeight )
307+ if err != nil {
308+ if errors .Is (err , coreda .ErrBlobNotFound ) {
309+ // no data at this height, increase DA height
310+ currentDAHeight ++
311+ s .SetDAHeight (currentDAHeight )
312+ continue
313+ }
314+ s .logger .Error ().Err (err ).Uint64 ("da_height" , currentDAHeight ).Msg ("failed to retrieve from DA during sync" )
267315 return
268- default :
269316 }
270317
271- s .processPendingEvents ()
272- s .tryFetchFromP2P ()
273- s .tryFetchFromDA (nextDARequestAt )
318+ // Process DA events
319+ for _ , event := range events {
320+ select {
321+ case <- s .ctx .Done ():
322+ return
323+ case s .heightInCh <- event :
324+ default :
325+ s .cache .SetPendingEvent (event .Header .Height (), & event )
326+ }
327+ }
328+
329+ // increase DA height
330+ currentDAHeight ++
331+ s .SetDAHeight (currentDAHeight )
332+ }
333+ }
334+
335+ func (s * Syncer ) daRetrievalLoop () {
336+ s .logger .Info ().Msg ("starting DA retrieval loop" )
337+ defer s .logger .Info ().Msg ("DA retrieval loop stopped" )
274338
275- // Prevent busy-waiting when no events are processed
339+ // Backoff control when DA replies with errors
340+ nextDARequestAt := & time.Time {}
341+
342+ // TODO @tac0turtle, changed it to fire on Celestia block time
343+ ticker := time .NewTicker (time .Second * 6 )
344+ defer ticker .Stop ()
345+
346+ for {
276347 select {
277348 case <- s .ctx .Done ():
278349 return
279- case <- time .After (min (10 * time .Millisecond , s .config .Node .BlockTime .Duration )):
350+ case <- ticker .C :
351+ s .tryFetchFromDA (nextDARequestAt )
352+ s .processPendingEvents ()
353+ default :
280354 }
281355 }
282356}
@@ -333,28 +407,39 @@ func (s *Syncer) tryFetchFromDA(nextDARequestAt *time.Time) {
333407 s .SetDAHeight (daHeight + 1 )
334408}
335409
336- // tryFetchFromP2P attempts to fetch events from P2P stores.
337- // It processes both header and data ranges when the block ticker fires.
338- // Returns true if any events were successfully processed.
339- func (s * Syncer ) tryFetchFromP2P () {
340- currentHeight , err := s .store .Height (s .ctx )
341- if err != nil {
342- s .logger .Error ().Err (err ).Msg ("failed to get current height" )
343- return
344- }
410+ // waitForNewP2PHeights waits for new headers or data to appear in the p2p
411+ // header/data stores and processes them.
412+ func (s * Syncer ) waitForNewP2PHeights () {
413+ // TODO @tac0turtle: ev-node expected blocktime here
414+ ticker := time .NewTicker (time .Millisecond * 100 )
415+ defer ticker .Stop ()
345416
346- // Process headers
347- newHeaderHeight := s .headerStore .Store ().Height ()
348- if newHeaderHeight > currentHeight {
349- s .p2pHandler .ProcessHeaderRange (s .ctx , currentHeight + 1 , newHeaderHeight , s .heightInCh )
350- }
417+ for {
418+ select {
419+ case <- s .ctx .Done ():
420+ return
421+ default :
422+ }
423+
424+ syncHeadHeader , err := s .headerStore .SyncHead ()
425+ if err != nil {
426+ s .logger .Error ().Err (err ).Msg ("failed to get header p2p sync head" )
427+ continue
428+ }
351429
352- // Process data (if not already processed by headers)
353- newDataHeight := s .dataStore .Store ().Height ()
354- // TODO @MARKO: why only if newDataHeight != newHeaderHeight? why not process
355- // just if newDataHeight > currentHeight ?
356- if newDataHeight != newHeaderHeight && newDataHeight > currentHeight {
357- s .p2pHandler .ProcessDataRange (s .ctx , currentHeight + 1 , newDataHeight , s .heightInCh )
430+ // try to process all headers between store's height and the syncer's head
431+ s .p2pHandler .ProcessHeaderRange (s .ctx , s .headerStore .Store ().Height ()+ 1 , syncHeadHeader .Height (), s .heightInCh )
432+
433+ // process data (if not already processed by headers)
434+ syncHeadData , err := s .dataStore .SyncHead ()
435+ if err != nil {
436+ s .logger .Error ().Err (err ).Msg ("failed to get data p2p sync head" )
437+ continue
438+ }
439+
440+ if s .dataStore .Store ().Height () < syncHeadData .Height () {
441+ s .p2pHandler .ProcessDataRange (s .ctx , s .dataStore .Store ().Height ()+ 1 , syncHeadData .Height (), s .heightInCh )
442+ }
358443 }
359444}
360445
@@ -636,9 +721,6 @@ func (s *Syncer) processPendingEvents() {
636721 case s .heightInCh <- heightEvent :
637722 // Event was successfully sent and already removed by GetNextPendingEvent
638723 s .logger .Debug ().Uint64 ("height" , nextHeight ).Msg ("sent pending event to processing" )
639- case <- s .ctx .Done ():
640- s .cache .SetPendingEvent (nextHeight , event )
641- return
642724 default :
643725 s .cache .SetPendingEvent (nextHeight , event )
644726 return
0 commit comments