@@ -3,6 +3,7 @@ package sync
3
3
import (
4
4
"context"
5
5
"errors"
6
+ "fmt"
6
7
"time"
7
8
8
9
"github.com/celestiaorg/go-header"
@@ -76,15 +77,14 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
76
77
}
77
78
// if pending is empty - get the latest stored/synced head
78
79
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 :
80
86
return storeHead , err
81
87
}
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 ())
88
88
// single-flight protection
89
89
// ensure only one Head is requested at the time
90
90
if ! s .getter .Lock () {
@@ -98,6 +98,16 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
98
98
if err != nil {
99
99
return trustHead , err
100
100
}
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
+
101
111
s .metrics .subjectiveInitialization (s .ctx )
102
112
// and set it as the new subjective head without validation,
103
113
// or, in other words, do 'automatic subjective initialization'
@@ -117,6 +127,32 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
117
127
return trustHead , nil
118
128
}
119
129
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
+
120
156
// setSubjectiveHead takes already validated head and sets it as the new sync target.
121
157
func (s * Syncer [H ]) setSubjectiveHead (ctx context.Context , netHead H ) {
122
158
// 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
207
243
}
208
244
return time .Since (header .Time ()) <= recencyThreshold
209
245
}
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