@@ -22,7 +22,7 @@ import (
2222// - followLoop listens on the subscription channel. When caught up, it processes
2323// subscription blobs inline (fast path, no DA re-fetch). Otherwise, it updates
2424// highestSeenDAHeight and signals the catchup loop.
25- // - catchupLoop sequentially retrieves from localDAHeight → highestSeenDAHeight,
25+ // - catchupLoop sequentially retrieves from localNextDAHeight → highestSeenDAHeight,
2626// piping events to the Syncer's heightInCh.
2727//
2828// The two goroutines share only atomic state; no mutexes needed.
@@ -51,9 +51,9 @@ type daFollower struct {
5151 // share the same namespace). When different, we subscribe to both and merge.
5252 dataNamespace []byte
5353
54- // localDAHeight is only written by catchupLoop and read by followLoop
54+ // localNextDAHeight is only written by catchupLoop and read by followLoop
5555 // to determine whether a catchup is needed.
56- localDAHeight atomic.Uint64
56+ localNextDAHeight atomic.Uint64
5757
5858 // highestSeenDAHeight is written by followLoop and read by catchupLoop.
5959 highestSeenDAHeight atomic.Uint64
@@ -62,7 +62,7 @@ type daFollower struct {
6262 headReached atomic.Bool
6363
6464 // catchupSignal is sent by followLoop to wake catchupLoop when a new
65- // height is seen that is above localDAHeight .
65+ // height is seen that is above localNextDAHeight .
6666 catchupSignal chan struct {}
6767
6868 // daBlockTime is used as a backoff before retrying after errors.
@@ -101,7 +101,7 @@ func NewDAFollower(cfg DAFollowerConfig) DAFollower {
101101 catchupSignal : make (chan struct {}, 1 ),
102102 daBlockTime : cfg .DABlockTime ,
103103 }
104- f .localDAHeight .Store (cfg .StartDAHeight )
104+ f .localNextDAHeight .Store (cfg .StartDAHeight )
105105 return f
106106}
107107
@@ -114,7 +114,7 @@ func (f *daFollower) Start(ctx context.Context) error {
114114 go f .catchupLoop (ctx )
115115
116116 f .logger .Info ().
117- Uint64 ("start_da_height" , f .localDAHeight .Load ()).
117+ Uint64 ("start_da_height" , f .localNextDAHeight .Load ()).
118118 Msg ("DA follower started" )
119119 return nil
120120}
@@ -142,7 +142,7 @@ func (f *daFollower) signalCatchup() {
142142}
143143
144144// followLoop subscribes to DA blob events and keeps highestSeenDAHeight up to date.
145- // When a new height appears above localDAHeight , it wakes the catchup loop.
145+ // When a new height appears above localNextDAHeight , it wakes the catchup loop.
146146func (f * daFollower ) followLoop (ctx context.Context ) {
147147 defer f .wg .Done ()
148148
@@ -244,11 +244,11 @@ func (f *daFollower) mergeSubscriptions(
244244}
245245
246246// handleSubscriptionEvent processes a subscription event. When the follower is
247- // caught up (ev.Height == localDAHeight ) and blobs are available, it processes
247+ // caught up (ev.Height == localNextDAHeight ) and blobs are available, it processes
248248// them inline — avoiding a DA re-fetch round trip. Otherwise, it just updates
249249// highestSeenDAHeight and lets catchupLoop handle retrieval.
250250//
251- // Uses CAS on localDAHeight to claim exclusive access to processBlobs,
251+ // Uses CAS on localNextDAHeight to claim exclusive access to processBlobs,
252252// preventing concurrent map access with catchupLoop.
253253func (f * daFollower ) handleSubscriptionEvent (ctx context.Context , ev datypes.SubscriptionEvent ) {
254254 // Always record the highest height we've seen from the subscription.
@@ -257,24 +257,26 @@ func (f *daFollower) handleSubscriptionEvent(ctx context.Context, ev datypes.Sub
257257 // Fast path: try to claim this height for inline processing.
258258 // CAS(N, N+1) ensures only one goroutine (followLoop or catchupLoop)
259259 // can enter processBlobs for height N.
260- if len (ev .Blobs ) > 0 && f .localDAHeight .CompareAndSwap (ev .Height , ev .Height + 1 ) {
260+ if len (ev .Blobs ) > 0 && f .localNextDAHeight .CompareAndSwap (ev .Height , ev .Height + 1 ) {
261261 events := f .retriever .ProcessBlobs (ctx , ev .Blobs , ev .Height )
262262 for _ , event := range events {
263263 if err := f .pipeEvent (ctx , event ); err != nil {
264264 // Roll back so catchupLoop can retry this height.
265- f .localDAHeight .Store (ev .Height )
265+ f .localNextDAHeight .Store (ev .Height )
266266 f .logger .Warn ().Err (err ).Uint64 ("da_height" , ev .Height ).
267267 Msg ("failed to pipe inline event, catchup will retry" )
268268 return
269269 }
270270 }
271271 if len (events ) != 0 {
272- f .headReached .Store (true )
272+ if ! f .headReached .Load () && f .localNextDAHeight .Load () > f .highestSeenDAHeight .Load () {
273+ f .headReached .Store (true )
274+ }
273275 f .logger .Debug ().Uint64 ("da_height" , ev .Height ).Int ("events" , len (events )).
274276 Msg ("processed subscription blobs inline (fast path)" )
275277 } else {
276278 // No complete events (split namespace, waiting for other half).
277- f .localDAHeight .Store (ev .Height )
279+ f .localNextDAHeight .Store (ev .Height )
278280 }
279281 return
280282 }
@@ -297,7 +299,7 @@ func (f *daFollower) updateHighest(height uint64) {
297299}
298300
299301// catchupLoop waits for signals and sequentially retrieves DA heights
300- // from localDAHeight up to highestSeenDAHeight.
302+ // from localNextDAHeight up to highestSeenDAHeight.
301303func (f * daFollower ) catchupLoop (ctx context.Context ) {
302304 defer f .wg .Done ()
303305
@@ -314,7 +316,7 @@ func (f *daFollower) catchupLoop(ctx context.Context) {
314316 }
315317}
316318
317- // runCatchup sequentially retrieves from localDAHeight to highestSeenDAHeight.
319+ // runCatchup sequentially retrieves from localNextDAHeight to highestSeenDAHeight.
318320// It handles priority heights first, then sequential heights.
319321func (f * daFollower ) runCatchup (ctx context.Context ) {
320322 for {
@@ -324,8 +326,8 @@ func (f *daFollower) runCatchup(ctx context.Context) {
324326
325327 // Check for priority heights from P2P hints first.
326328 if priorityHeight := f .retriever .PopPriorityHeight (); priorityHeight > 0 {
327- currentHeight := f .localDAHeight .Load ()
328- if priorityHeight < currentHeight {
329+ nextHeight := f .localNextDAHeight .Load ()
330+ if priorityHeight < nextHeight {
329331 continue
330332 }
331333 f .logger .Debug ().
@@ -340,7 +342,7 @@ func (f *daFollower) runCatchup(ctx context.Context) {
340342 }
341343
342344 // Sequential catchup.
343- local := f .localDAHeight .Load ()
345+ local := f .localNextDAHeight .Load ()
344346 highest := f .highestSeenDAHeight .Load ()
345347
346348 if local > highest {
@@ -350,14 +352,14 @@ func (f *daFollower) runCatchup(ctx context.Context) {
350352 }
351353
352354 // CAS claims this height prevents followLoop from inline-processing
353- if ! f .localDAHeight .CompareAndSwap (local , local + 1 ) {
355+ if ! f .localNextDAHeight .CompareAndSwap (local , local + 1 ) {
354356 // followLoop already advanced past this height via inline processing.
355357 continue
356358 }
357359
358360 if err := f .fetchAndPipeHeight (ctx , local ); err != nil {
359361 // Roll back so we can retry after backoff.
360- f .localDAHeight .Store (local )
362+ f .localNextDAHeight .Store (local )
361363 if ! f .waitOnCatchupError (ctx , err , local ) {
362364 return
363365 }
0 commit comments