Skip to content

Commit 9df4ff9

Browse files
committed
Updates - WIP
1 parent 239157f commit 9df4ff9

File tree

6 files changed

+134
-131
lines changed

6 files changed

+134
-131
lines changed

block/internal/da/async_block_retriever.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ func NewAsyncBlockRetriever(
8888
DABlockTime: daBlockTime,
8989
Handler: f,
9090
FetchBlockTimestamp: true,
91+
StartHeight: daStartHeight,
9192
})
92-
f.subscriber.SetStartHeight(daStartHeight)
9393

9494
return f
9595
}
@@ -173,8 +173,12 @@ func (f *asyncBlockRetriever) GetCachedBlock(ctx context.Context, daHeight uint6
173173

174174
// HandleEvent caches blobs from the subscription inline, even empty ones,
175175
// to record that the DA height was seen and has 0 blobs.
176-
func (f *asyncBlockRetriever) HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent) {
176+
func (f *asyncBlockRetriever) HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent, isInline bool) error {
177177
f.cacheBlock(ctx, ev.Height, ev.Timestamp, ev.Blobs)
178+
if isInline {
179+
return errors.New("async block retriever relies on catchup state machine")
180+
}
181+
return nil
178182
}
179183

180184
// HandleCatchup fetches a single height via Retrieve and caches it.

block/internal/da/subscriber.go

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import (
1717
// SubscriberHandler is the callback interface for subscription consumers.
1818
// Implementations drive the consumer-specific logic (caching, piping events, etc.).
1919
type SubscriberHandler interface {
20-
// HandleEvent processes a subscription event inline (fast path).
21-
// Called on the followLoop goroutine for each subscription event.
22-
HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent)
20+
// HandleEvent processes a subscription event.
21+
// isInline is true if the subscriber successfully claimed this height (via CAS).
22+
// Returning an error when isInline is true instructs the Subscriber to roll back the localDAHeight.
23+
HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent, isInline bool) error
2324

2425
// HandleCatchup is called for each height during sequential catchup.
25-
// The subscriber advances localDAHeight only after this returns nil.
26-
// Returning an error triggers a backoff retry.
26+
// The subscriber advances localDAHeight only after this returns (true, nil).
27+
// Returning (false, nil) rolls back localDAHeight without triggering a backoff.
28+
// Returning an error rolls back localDAHeight and triggers a backoff retry.
2729
HandleCatchup(ctx context.Context, height uint64) error
2830
}
2931

@@ -36,6 +38,8 @@ type SubscriberConfig struct {
3638
Handler SubscriberHandler
3739
// Deprecated: Remove with https://github.com/evstack/ev-node/issues/3142
3840
FetchBlockTimestamp bool // the timestamp comes with an extra api call before Celestia v0.29.1-mocha.
41+
42+
StartHeight uint64 // initial localDAHeight
3943
}
4044

4145
// Subscriber is a shared DA subscription primitive that encapsulates the
@@ -87,17 +91,15 @@ func NewSubscriber(cfg SubscriberConfig) *Subscriber {
8791
daBlockTime: cfg.DABlockTime,
8892
fetchBlockTimestamp: cfg.FetchBlockTimestamp,
8993
}
94+
s.localDAHeight.Store(cfg.StartHeight)
95+
s.catchupSignal <- struct{}{}
96+
9097
if len(s.namespaces) == 0 {
9198
s.logger.Warn().Msg("no namespaces configured, subscriber will stay idle")
9299
}
93100
return s
94101
}
95102

96-
// SetStartHeight sets the initial local DA height before Start is called.
97-
func (s *Subscriber) SetStartHeight(height uint64) {
98-
s.localDAHeight.Store(height)
99-
}
100-
101103
// Start begins the follow and catchup goroutines.
102104
func (s *Subscriber) Start(ctx context.Context) error {
103105
if len(s.namespaces) == 0 {
@@ -108,6 +110,7 @@ func (s *Subscriber) Start(ctx context.Context) error {
108110
s.wg.Add(2)
109111
go s.followLoop(ctx)
110112
go s.catchupLoop(ctx)
113+
111114
return nil
112115
}
113116

@@ -134,29 +137,6 @@ func (s *Subscriber) HasReachedHead() bool {
134137
return s.headReached.Load()
135138
}
136139

137-
// CompareAndSwapLocalHeight attempts a CAS on localDAHeight.
138-
// Used by handlers that want to claim exclusive processing of a height.
139-
func (s *Subscriber) CompareAndSwapLocalHeight(old, new uint64) bool {
140-
return s.localDAHeight.CompareAndSwap(old, new)
141-
}
142-
143-
// SetLocalHeight stores a new localDAHeight value.
144-
func (s *Subscriber) SetLocalHeight(height uint64) {
145-
s.localDAHeight.Store(height)
146-
}
147-
148-
// UpdateHighestForTest directly sets the highest seen DA height and signals catchup.
149-
// Only for use in tests that bypass the subscription loop.
150-
func (s *Subscriber) UpdateHighestForTest(height uint64) {
151-
s.updateHighest(height)
152-
}
153-
154-
// RunCatchupForTest runs a single catchup pass. Only for use in tests that
155-
// bypass the catchup loop's signal-wait mechanism.
156-
func (s *Subscriber) RunCatchupForTest(ctx context.Context) {
157-
s.runCatchup(ctx)
158-
}
159-
160140
// ---------------------------------------------------------------------------
161141
// Follow loop
162142
// ---------------------------------------------------------------------------
@@ -215,7 +195,21 @@ func (s *Subscriber) runSubscription(ctx context.Context) error {
215195
return errors.New("subscription channel closed")
216196
}
217197
s.updateHighest(ev.Height)
218-
s.handler.HandleEvent(ctx, ev)
198+
199+
local := s.localDAHeight.Load()
200+
isInline := ev.Height == local && s.localDAHeight.CompareAndSwap(local, local+1)
201+
202+
err = s.handler.HandleEvent(ctx, ev, isInline)
203+
if isInline {
204+
if err != nil {
205+
s.localDAHeight.Store(local)
206+
s.logger.Debug().Err(err).Uint64("da_height", ev.Height).
207+
Msg("inline processing skipped/failed, rolling back")
208+
} else {
209+
s.headReached.Store(true)
210+
}
211+
}
212+
219213
watchdog.Reset(watchdogTimeout)
220214
case <-watchdog.C:
221215
return errors.New("subscription watchdog: no events received, reconnecting")
@@ -325,9 +319,7 @@ func (s *Subscriber) runCatchup(ctx context.Context) {
325319
}
326320

327321
local := s.localDAHeight.Load()
328-
highest := s.highestSeenDAHeight.Load()
329-
330-
if local > highest {
322+
if local > s.highestSeenDAHeight.Load() {
331323
s.headReached.Store(true)
332324
return
333325
}

block/internal/syncing/da_follower.go

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ func NewDAFollower(cfg DAFollowerConfig) DAFollower {
6868
Namespaces: [][]byte{cfg.Namespace, dataNs},
6969
DABlockTime: cfg.DABlockTime,
7070
Handler: f,
71+
StartHeight: cfg.StartDAHeight,
7172
})
72-
f.subscriber.SetStartHeight(cfg.StartDAHeight)
7373

7474
return f
7575
}
@@ -97,35 +97,27 @@ func (f *daFollower) HasReachedHead() bool {
9797
// caught up (ev.Height == localDAHeight) and blobs are available, it processes
9898
// them inline — avoiding a DA re-fetch round trip. Otherwise, it just lets
9999
// the catchup loop handle retrieval.
100-
//
101-
// Uses CAS on localDAHeight to claim exclusive access to processBlobs,
102-
// preventing concurrent map access with catchupLoop.
103-
func (f *daFollower) HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent) {
104-
// Fast path: try to claim this height for inline processing.
105-
// CAS(N, N+1) ensures only one goroutine (followLoop or catchupLoop)
106-
// can enter processBlobs for height N.
107-
if len(ev.Blobs) > 0 && f.subscriber.CompareAndSwapLocalHeight(ev.Height, ev.Height+1) {
108-
events := f.retriever.ProcessBlobs(ctx, ev.Blobs, ev.Height)
109-
for _, event := range events {
110-
if err := f.eventSink.PipeEvent(ctx, event); err != nil {
111-
// Roll back so catchupLoop can retry this height.
112-
f.subscriber.SetLocalHeight(ev.Height)
113-
f.logger.Warn().Err(err).Uint64("da_height", ev.Height).
114-
Msg("failed to pipe inline event, catchup will retry")
115-
return
116-
}
117-
}
118-
if len(events) != 0 {
119-
f.logger.Debug().Uint64("da_height", ev.Height).Int("events", len(events)).
120-
Msg("processed subscription blobs inline (fast path)")
121-
} else {
122-
// No complete events (split namespace, waiting for other half).
123-
f.subscriber.SetLocalHeight(ev.Height)
100+
func (f *daFollower) HandleEvent(ctx context.Context, ev datypes.SubscriptionEvent, isInline bool) error {
101+
if !isInline || len(ev.Blobs) == 0 {
102+
return nil // skip
103+
}
104+
105+
events := f.retriever.ProcessBlobs(ctx, ev.Blobs, ev.Height)
106+
if len(events) == 0 {
107+
return errors.New("skip inline: no complete events") // Split namespace, subscriber rolls back
108+
}
109+
110+
for _, event := range events {
111+
if err := f.eventSink.PipeEvent(ctx, event); err != nil {
112+
f.logger.Warn().Err(err).Uint64("da_height", ev.Height).
113+
Msg("failed to pipe inline event, catchup will retry")
114+
return err // Actual pipe failure, subscriber rolls back
124115
}
125-
return
126116
}
127117

128-
// Slow path: behind, no blobs, or catchupLoop claimed this height.
118+
f.logger.Debug().Uint64("da_height", ev.Height).Int("events", len(events)).
119+
Msg("processed subscription blobs inline (fast path)")
120+
return nil
129121
}
130122

131123
// HandleCatchup retrieves events at a single DA height and pipes them
@@ -138,11 +130,7 @@ func (f *daFollower) HandleEvent(ctx context.Context, ev datypes.SubscriptionEve
138130
// skipping the current daHeight or stopping catchup.
139131
func (f *daFollower) HandleCatchup(ctx context.Context, daHeight uint64) error {
140132
// 1. Drain stale or future priority heights from P2P hints
141-
for {
142-
priorityHeight := f.popPriorityHeight()
143-
if priorityHeight == 0 {
144-
break
145-
}
133+
for priorityHeight := f.popPriorityHeight(); priorityHeight != 0; priorityHeight = f.popPriorityHeight() {
146134
if priorityHeight < daHeight {
147135
continue // skip stale hints without yielding back to the catchup loop
148136
}
@@ -159,14 +147,9 @@ func (f *daFollower) HandleCatchup(ctx context.Context, daHeight uint64) error {
159147
continue
160148
}
161149
// Roll back so daHeight is attempted again next cycle after backoff.
162-
f.subscriber.SetLocalHeight(daHeight)
163150
return err
164151
}
165-
166-
// We successfully handled a priority height (we didn't actually process `daHeight`)
167-
// Roll back so daHeight is attempted again next cycle.
168-
f.subscriber.SetLocalHeight(daHeight)
169-
return nil
152+
break // continue with daHeight
170153
}
171154

172155
// 2. Normal sequential fetch

0 commit comments

Comments
 (0)