Skip to content

Commit 8e61f29

Browse files
committed
trying to clean up the syncer.go
1 parent 31859a7 commit 8e61f29

5 files changed

Lines changed: 146 additions & 63 deletions

File tree

block/internal/common/expected_interfaces.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import (
77
"github.com/celestiaorg/go-header"
88
)
99

10-
// broadcaster interface for P2P broadcasting
10+
// Broadcaster interface for P2P broadcasting
1111
type Broadcaster[H header.Header[H]] interface {
1212
WriteToStoreAndBroadcast(ctx context.Context, payload H, opts ...pubsub.PubOpt) error
1313
Store() header.Store[H]
14+
SyncHead() (H, error)
1415
}

block/internal/syncing/p2p_handler.go

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,9 @@ func (h *P2PHandler) ProcessHeaderRange(ctx context.Context, startHeight, endHei
5555
default:
5656
}
5757

58-
// Create a timeout context for each GetByHeight call to prevent blocking
59-
timeoutCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
60-
header, err := h.headerStore.GetByHeight(timeoutCtx, height)
61-
cancel()
62-
58+
// TODO @tac0turtle: it is desirable to block here as go-header store automatically subscribes to the new height
59+
// if it is not available, and returns it when it becomes available (appended)
60+
header, err := h.headerStore.GetByHeight(ctx, height)
6361
if err != nil {
6462
if errors.Is(err, context.DeadlineExceeded) {
6563
h.logger.Debug().Uint64("height", height).Msg("timeout waiting for header from store, will retry later")
@@ -79,10 +77,12 @@ func (h *P2PHandler) ProcessHeaderRange(ctx context.Context, startHeight, endHei
7977

8078
// Get corresponding data (empty data are still broadcasted by peers)
8179
var data *types.Data
82-
timeoutCtx, cancel = context.WithTimeout(ctx, 500*time.Millisecond)
80+
81+
// TODO @tac0turtle: we can provide a sane timeout here bc we expect header + data not to
82+
// drift too much
83+
timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*2)
8384
retrievedData, err := h.dataStore.GetByHeight(timeoutCtx, height)
8485
cancel()
85-
8686
if err != nil {
8787
if errors.Is(err, context.DeadlineExceeded) {
8888
h.logger.Debug().Uint64("height", height).Msg("timeout waiting for data from store, will retry later")
@@ -141,11 +141,8 @@ func (h *P2PHandler) ProcessDataRange(ctx context.Context, startHeight, endHeigh
141141
default:
142142
}
143143

144-
// Create a timeout context for each GetByHeight call to prevent blocking
145-
timeoutCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
146-
data, err := h.dataStore.GetByHeight(timeoutCtx, height)
147-
cancel()
148-
144+
// TODO @tac0turtle: same here
145+
data, err := h.dataStore.GetByHeight(ctx, height)
149146
if err != nil {
150147
if errors.Is(err, context.DeadlineExceeded) {
151148
h.logger.Debug().Uint64("height", height).Msg("timeout waiting for data from store, will retry later")
@@ -158,10 +155,9 @@ func (h *P2PHandler) ProcessDataRange(ctx context.Context, startHeight, endHeigh
158155
}
159156

160157
// Get corresponding header with timeout
161-
timeoutCtx, cancel = context.WithTimeout(ctx, 500*time.Millisecond)
158+
timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*2)
162159
header, err := h.headerStore.GetByHeight(timeoutCtx, height)
163160
cancel()
164-
165161
if err != nil {
166162
if errors.Is(err, context.DeadlineExceeded) {
167163
h.logger.Debug().Uint64("height", height).Msg("timeout waiting for header from store, will retry later")

block/internal/syncing/syncer.go

Lines changed: 129 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
248246
func (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

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/evstack/ev-node
22

3-
go 1.24.6
3+
go 1.25.1
44

55
retract v0.12.0 // Published by accident
66

pkg/sync/sync_service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ func newSyncService[H header.Header[H]](
116116
}, nil
117117
}
118118

119+
func (syncService *SyncService[H]) SyncHead(ctx context.Context) (H, error) {
120+
return syncService.syncer.Head(ctx)
121+
}
122+
119123
// Store returns the store of the SyncService
120124
func (syncService *SyncService[H]) Store() header.Store[H] {
121125
return syncService.store

0 commit comments

Comments
 (0)