Skip to content

Commit 31d7e13

Browse files
committed
last refactor beautifcation
1 parent 630ffa3 commit 31d7e13

File tree

2 files changed

+146
-94
lines changed

2 files changed

+146
-94
lines changed

sync/sync_tail.go

Lines changed: 145 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -5,112 +5,100 @@ import (
55
"context"
66
"errors"
77
"fmt"
8-
"time"
98

109
"github.com/celestiaorg/go-header"
1110
)
1211

13-
// TODO:
14-
// * Refactor tests
15-
16-
// subjectiveTail returns the current Tail header.
12+
// subjectiveTail returns the current actual Tail header.
1713
// Lazily fetching it if it doesn't exist locally or moving it to a different height.
1814
// Moving is done if either parameters are changed or tail moved outside a pruning window.
1915
func (s *Syncer[H]) subjectiveTail(ctx context.Context, head H) (H, error) {
20-
tail, err := s.store.Tail(ctx)
16+
oldTail, err := s.store.Tail(ctx)
2117
if err != nil && !errors.Is(err, header.ErrEmptyStore) {
22-
return tail, err
18+
return oldTail, err
19+
}
20+
21+
newTail, err := s.updateTail(ctx, oldTail, head)
22+
if err != nil {
23+
return oldTail, fmt.Errorf("updating tail: %w", err)
2324
}
2425

25-
var fetched bool
26-
if tailHash, outdated := s.isTailHashOutdated(tail); outdated {
27-
log.Debugw("tail hash updated", "hash", tailHash)
28-
tail, err = s.store.Get(ctx, tailHash)
26+
if err := s.moveTail(ctx, oldTail, newTail); err != nil {
27+
return oldTail, fmt.Errorf(
28+
"moving tail from %d to %d: %w",
29+
oldTail.Height(),
30+
newTail.Height(),
31+
err,
32+
)
33+
}
34+
35+
return newTail, nil
36+
}
37+
38+
// updateTail updates the tail header based on the Syncer parameters.
39+
func (s *Syncer[H]) updateTail(ctx context.Context, oldTail, head H) (newTail H, err error) {
40+
switch tailHash := s.tailHash(oldTail); tailHash {
41+
case nil:
42+
tailHeight, err := s.tailHeight(ctx, oldTail, head)
2943
if err != nil {
30-
log.Debugw("tail hash not available locally, fetching...", "hash", tailHash)
31-
tail, err = s.getter.Get(ctx, tailHash)
32-
if err != nil {
33-
return tail, fmt.Errorf("getting SyncFromHash tail(%x): %w", tailHash, err)
34-
}
35-
fetched = true
44+
return oldTail, err
3645
}
37-
} else if tailHeight, outdated := s.isTailHeightOutdated(tail); outdated {
38-
log.Debugw("tail height updated", "height", tailHeight)
46+
3947
if tailHeight <= s.store.Height() {
40-
tail, err = s.store.GetByHeight(ctx, tailHeight)
41-
}
42-
if err != nil || tailHeight != tail.Height() {
43-
log.Debugw("tail height not available locally, fetching...", "height", tailHeight)
44-
tail, err = s.getter.GetByHeight(ctx, tailHeight)
45-
if err != nil {
46-
return tail, fmt.Errorf("getting SyncFromHeight tail(%d): %w", tailHeight, err)
47-
}
48-
fetched = true
49-
}
50-
} else if tailHash == nil && tailHeight == 0 {
51-
if tail.IsZero() {
52-
// no previously known Tail available - estimate solely on Head
53-
estimatedHeight := estimateTail(head, s.Params.blockTime, s.Params.TrustingPeriod)
54-
tail, err = s.getter.GetByHeight(ctx, estimatedHeight)
55-
if err != nil {
56-
return tail, fmt.Errorf("getting estimated tail(%d): %w", tailHeight, err)
48+
// check if the new tail is below the current head to avoid heightSub blocking
49+
newTail, err = s.store.GetByHeight(ctx, tailHeight)
50+
if err == nil {
51+
return newTail, nil
5752
}
58-
fetched = true
59-
} else {
60-
// have a known Tail - estimate basing on it.
61-
cutoffTime := head.Time().UTC().Add(-s.Params.PruningWindow)
62-
diff := cutoffTime.Sub(tail.Time().UTC())
63-
if diff <= 0 {
64-
// current tail is relevant as is
65-
return tail, nil
66-
}
67-
log.Debugw("current tail is beyond pruning window", "current_height", tail.Height(), "diff", diff.String())
68-
69-
toDeleteEstimate := uint64(diff / s.Params.blockTime) //nolint:gosec
70-
estimatedNewTail := tail.Height() + toDeleteEstimate
71-
72-
for {
73-
tail, err = s.store.GetByHeight(ctx, estimatedNewTail)
74-
if err != nil {
75-
log.Errorw("getting estimated tail from store", "height", estimatedNewTail, "error", err)
76-
return tail, err
77-
}
78-
if tail.Time().UTC().Compare(cutoffTime) <= 0 {
79-
// tail before or equal to cutoffTime
80-
break
81-
}
82-
83-
estimatedNewTail++
53+
if !errors.Is(err, header.ErrNotFound) {
54+
return newTail, fmt.Errorf(
55+
"loading SyncFromHeight tail from store(%d): %w",
56+
tailHeight,
57+
err,
58+
)
8459
}
60+
}
8561

86-
log.Debugw("estimated new tail", "new_height", tail.Height())
62+
log.Debugw("tail height not available locally, fetching...", "height", tailHeight)
63+
newTail, err = s.getter.GetByHeight(ctx, tailHeight)
64+
if err != nil {
65+
return newTail, fmt.Errorf("fetching SyncFromHeight tail(%d): %w", tailHeight, err)
66+
}
67+
default:
68+
newTail, err = s.store.Get(ctx, tailHash)
69+
if err == nil {
70+
return newTail, nil
71+
}
72+
if !errors.Is(err, header.ErrNotFound) {
73+
return newTail, fmt.Errorf(
74+
"loading SyncFromHash tail from store(%x): %w",
75+
tailHash,
76+
err,
77+
)
8778
}
88-
}
8979

90-
if fetched {
91-
if err := s.store.Append(ctx, tail); err != nil {
92-
return tail, fmt.Errorf("appending tail header: %w", err)
80+
log.Debugw("tail hash not available locally, fetching...", "hash", tailHash)
81+
newTail, err = s.getter.Get(ctx, tailHash)
82+
if err != nil {
83+
return newTail, fmt.Errorf("fetching SyncFromHash tail(%x): %w", tailHash, err)
9384
}
9485
}
9586

96-
if err := s.moveTail(ctx, tail); err != nil {
97-
return tail, fmt.Errorf("moving tail: %w", err)
87+
if err := s.store.Append(ctx, newTail); err != nil {
88+
return newTail, fmt.Errorf("appending tail header: %w", err)
9889
}
9990

100-
return tail, nil
91+
return newTail, nil
10192
}
10293

10394
// moveTail moves the Tail to be the given header.
10495
// It will prune the store if the new Tail is higher than the old one or
105-
// sync up if the new Tail is lower than the old one.
106-
func (s *Syncer[H]) moveTail(ctx context.Context, to H) error {
107-
from, err := s.store.Tail(ctx)
108-
if errors.Is(err, header.ErrEmptyStore) {
96+
// sync up the difference if the new Tail is lower than the old one.
97+
func (s *Syncer[H]) moveTail(ctx context.Context, from, to H) error {
98+
if from.IsZero() {
99+
// no need to move the tail if it was not set previously
109100
return nil
110101
}
111-
if err != nil {
112-
return err
113-
}
114102

115103
switch {
116104
case from.Height() < to.Height():
@@ -138,27 +126,91 @@ func (s *Syncer[H]) moveTail(ctx context.Context, to H) error {
138126
return nil
139127
}
140128

141-
func estimateTail[H header.Header[H]](
142-
head H,
143-
blockTime, trustingPeriod time.Duration,
144-
) (height uint64) {
145-
headersToRetain := uint64(trustingPeriod / blockTime) //nolint:gosec
129+
// tailHash returns the expected tail hash.
130+
// Does not return if the hash hasn't changed from the current tail hash.
131+
func (s *Syncer[H]) tailHash(oldTail H) header.Hash {
132+
hash := s.Params.SyncFromHash
133+
if hash == nil {
134+
return nil
135+
}
146136

137+
updated := oldTail.IsZero() || !bytes.Equal(hash, oldTail.Hash())
138+
if !updated {
139+
return nil
140+
}
141+
142+
log.Debugw("tail hash updated", "hash", hash)
143+
return hash
144+
}
145+
146+
// tailHeight figures the actual tail height based on the Syncer parameters.
147+
func (s *Syncer[H]) tailHeight(ctx context.Context, oldTail, head H) (uint64, error) {
148+
height := s.Params.SyncFromHeight
149+
if height > 0 {
150+
return height, nil
151+
}
152+
153+
if oldTail.IsZero() {
154+
return s.estimateTailHeader(head), nil
155+
}
156+
157+
height, err := s.findTailHeight(ctx, oldTail, head)
158+
if err != nil {
159+
return 0, fmt.Errorf("estimating oldTail height: %w", err)
160+
}
161+
162+
return height, nil
163+
}
164+
165+
// estimateTailHeader estimates the tail header based on the current head.
166+
// It respects the trusting period, ensuring Syncer never initializes off an expired header.
167+
func (s *Syncer[H]) estimateTailHeader(head H) uint64 {
168+
headersToRetain := uint64(s.Params.TrustingPeriod / s.Params.blockTime) //nolint:gosec
147169
if headersToRetain >= head.Height() {
170+
// means chain is very young so we can keep all headers starting from genesis
148171
return 1
149172
}
150-
tail := head.Height() - headersToRetain
151-
return tail
152-
}
153173

154-
func (s *Syncer[H]) isTailHashOutdated(h H) (header.Hash, bool) {
155-
wantHash := s.Params.SyncFromHash
156-
outdated := wantHash != nil && (h.IsZero() || !bytes.Equal(wantHash, h.Hash()))
157-
return wantHash, outdated
174+
return head.Height() - headersToRetain
158175
}
159176

160-
func (s *Syncer[H]) isTailHeightOutdated(h H) (uint64, bool) {
161-
wantHeight := s.Params.SyncFromHeight
162-
outdated := wantHeight > 0 && (h.IsZero() || h.Height() != wantHeight)
163-
return wantHeight, outdated
177+
// findTailHeight find the tail height based on the current head and tail.
178+
// It respects the pruning window, ensuring Syncer maintains the tail within the window.
179+
func (s *Syncer[H]) findTailHeight(ctx context.Context, oldTail, head H) (uint64, error) {
180+
expectedTailTime := head.Time().UTC().Add(-s.Params.PruningWindow)
181+
currentTailTime := oldTail.Time().UTC()
182+
183+
timeDiff := expectedTailTime.Sub(currentTailTime)
184+
if timeDiff <= 0 {
185+
// current tail is relevant as is
186+
return oldTail.Height(), nil
187+
}
188+
log.Debugw(
189+
"current tail is beyond pruning window",
190+
"tail_height", oldTail.Height(),
191+
"time_diff", timeDiff.String(),
192+
"window", s.Params.PruningWindow.String(),
193+
)
194+
195+
heightDiff := uint64(timeDiff / s.Params.blockTime) //nolint:gosec
196+
newTailHeight := oldTail.Height() + heightDiff
197+
for {
198+
newTail, err := s.store.GetByHeight(ctx, newTailHeight)
199+
if err != nil {
200+
return 0, fmt.Errorf(
201+
"getting estimated new tail(%d) from store: %w",
202+
newTailHeight,
203+
err,
204+
)
205+
}
206+
if newTail.Time().UTC().Compare(expectedTailTime) <= 0 {
207+
// oldTail before or equal to expectedTailTime
208+
break
209+
}
210+
211+
newTailHeight++
212+
}
213+
214+
log.Debugw("estimated new tail", "new_height", oldTail.Height())
215+
return newTailHeight, nil
164216
}

sync/sync_tail_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
)
1616

1717
func TestSyncer_TailEstimation(t *testing.T) {
18-
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
18+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*50)
1919
t.Cleanup(cancel)
2020

2121
remoteStore := headertest.NewStore[*headertest.DummyHeader](t, headertest.NewTestSuite(t), 100)

0 commit comments

Comments
 (0)