Skip to content

Commit 44a7fd9

Browse files
committed
feat(sync): subjectiveTail
1 parent 6418a17 commit 44a7fd9

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

sync/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package sync
33
import (
44
"fmt"
55
"time"
6+
7+
"github.com/celestiaorg/go-header"
68
)
79

810
// Option is the functional option that is applied to the Syner instance
@@ -20,6 +22,12 @@ type Parameters struct {
2022
// needed to report and punish misbehavior should be less than the unbonding
2123
// period.
2224
TrustingPeriod time.Duration
25+
// SyncFromHash is the hash of the header from which the syncer should start syncing.
26+
//
27+
// By default, the syncer will start syncing from Tail, height of which is identified by the
28+
// network head time minus TrustingPeriod. SyncFromHash overrides this default, allowing
29+
// user to specify a custom starting point.
30+
SyncFromHash header.Hash
2331
// blockTime provides a reference point for the Syncer to determine
2432
// whether its subjective head is outdated.
2533
// Keeping it private to disable serialization for it.

sync/sync_head.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package sync
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"time"
78

89
"github.com/celestiaorg/go-header"
@@ -76,15 +77,14 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
7677
}
7778
// if pending is empty - get the latest stored/synced head
7879
storeHead, err := s.store.Head(ctx)
79-
if err != nil {
80+
switch {
81+
case errors.Is(err, header.ErrNotFound):
82+
log.Infow("no stored head, initializing...", "height")
83+
case isExpired(storeHead, s.Params.TrustingPeriod):
84+
log.Infow("stored head header expired", "height", storeHead.Height())
85+
case err != nil:
8086
return storeHead, err
8187
}
82-
// check if the stored header is not expired and use it
83-
if !isExpired(storeHead, s.Params.TrustingPeriod) {
84-
return storeHead, nil
85-
}
86-
// otherwise, request head from a trusted peer
87-
log.Infow("stored head header expired", "height", storeHead.Height())
8888
// single-flight protection
8989
// ensure only one Head is requested at the time
9090
if !s.getter.Lock() {
@@ -98,6 +98,16 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
9898
if err != nil {
9999
return trustHead, err
100100
}
101+
102+
if errors.Is(err, header.ErrNotFound) {
103+
tail, err := s.subjectiveTail(ctx, trustHead)
104+
if err != nil {
105+
return tail, err
106+
}
107+
108+
// TODO: verify trustHead against tail
109+
}
110+
101111
s.metrics.subjectiveInitialization(s.ctx)
102112
// and set it as the new subjective head without validation,
103113
// or, in other words, do 'automatic subjective initialization'
@@ -117,6 +127,32 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
117127
return trustHead, nil
118128
}
119129

130+
func (s *Syncer[H]) subjectiveTail(ctx context.Context, trustHead H) (H, error) {
131+
var tail H
132+
var err error
133+
if s.Params.SyncFromHash != nil {
134+
tail, err = s.getter.Get(ctx, s.Params.SyncFromHash)
135+
if err != nil {
136+
return tail, fmt.Errorf("failed to get tail header: %w", err)
137+
}
138+
} else {
139+
// TODO(@Wondertan): as we using trustHead as a time reference point to estimate tail height
140+
// we should check if the trustHead is recent enough to estimate tail height
141+
tailHeight := estimateTail(trustHead, s.Params.blockTime, s.Params.TrustingPeriod)
142+
tail, err = s.getter.GetByHeight(ctx, tailHeight)
143+
if err != nil {
144+
return tail, fmt.Errorf("failed to get tail header(%d): %w", tailHeight, err)
145+
}
146+
}
147+
148+
err = s.store.Store.Append(ctx, tail)
149+
if err != nil {
150+
return tail, fmt.Errorf("failed to append tail header: %w", err)
151+
}
152+
153+
return tail, nil
154+
}
155+
120156
// setSubjectiveHead takes already validated head and sets it as the new sync target.
121157
func (s *Syncer[H]) setSubjectiveHead(ctx context.Context, netHead H) {
122158
// TODO(@Wondertan): Right now, we can only store adjacent headers, instead we should:
@@ -207,3 +243,9 @@ func isRecent[H header.Header[H]](header H, blockTime, recencyThreshold time.Dur
207243
}
208244
return time.Since(header.Time()) <= recencyThreshold
209245
}
246+
247+
func estimateTail[H header.Header[H]](head H, blockTime, trustingPeriod time.Duration) (height uint64) {
248+
headersToRetain := uint64(trustingPeriod / blockTime)
249+
tail := head.Height() - headersToRetain
250+
return tail
251+
}

0 commit comments

Comments
 (0)