1
1
package sync
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"errors"
6
7
"fmt"
@@ -56,6 +57,80 @@ func (s *Syncer[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, err
56
57
return s .subjectiveHead (ctx )
57
58
}
58
59
60
+ func (s * Syncer [H ]) Tail (ctx context.Context ) (H , error ) {
61
+ tail , err := s .store .Tail (ctx )
62
+ switch {
63
+ case errors .Is (err , header .ErrEmptyStore ):
64
+ switch {
65
+ case s .Params .SyncFromHash != nil :
66
+ tail , err = s .getter .Get (ctx , s .Params .SyncFromHash )
67
+ if err != nil {
68
+ return tail , fmt .Errorf ("getting tail header by hash(%s): %w" , s .Params .SyncFromHash , err )
69
+ }
70
+ case s .Params .SyncFromHeight != 0 :
71
+ tail , err = s .getter .GetByHeight (ctx , s .Params .SyncFromHeight )
72
+ if err != nil {
73
+ return tail , fmt .Errorf ("getting tail header(%d): %w" , s .Params .SyncFromHeight , err )
74
+ }
75
+ default :
76
+ head , err := s .Head (ctx )
77
+ if err != nil {
78
+ return head , err
79
+ }
80
+
81
+ tailHeight := estimateTail (head , s .Params .blockTime , s .Params .TrustingPeriod )
82
+ tail , err = s .getter .GetByHeight (ctx , tailHeight )
83
+ if err != nil {
84
+ return tail , fmt .Errorf ("getting estimated tail header(%d): %w" , tailHeight , err )
85
+ }
86
+ }
87
+
88
+ err = s .store .Store .Append (ctx , tail )
89
+ if err != nil {
90
+ return tail , fmt .Errorf ("appending tail header: %w" , err )
91
+ }
92
+
93
+ case ! s .isTailActual (tail ):
94
+ if s .Params .SyncFromHash != nil {
95
+ tail , err = s .getter .Get (ctx , s .Params .SyncFromHash )
96
+ if err != nil {
97
+ return tail , fmt .Errorf ("getting tail header by hash(%s): %w" , s .Params .SyncFromHash , err )
98
+ }
99
+ } else if s .Params .SyncFromHeight != 0 {
100
+ tail , err = s .getter .GetByHeight (ctx , s .Params .SyncFromHeight )
101
+ if err != nil {
102
+ return tail , fmt .Errorf ("getting tail header(%d): %w" , s .Params .SyncFromHeight , err )
103
+ }
104
+ }
105
+
106
+ // TODO: Delete or sync up the diff
107
+
108
+ case err != nil :
109
+ return tail , err
110
+ }
111
+
112
+ return tail , nil
113
+ }
114
+
115
+ // isTailActual checks if the given tail is actual based on the sync parameters.
116
+ func (s * Syncer [H ]) isTailActual (tail H ) bool {
117
+ if tail .IsZero () {
118
+ return false
119
+ }
120
+
121
+ switch {
122
+ case s .Params .SyncFromHash == nil && s .Params .SyncFromHeight == 0 :
123
+ // if both overrides are zero value, then we good with whatever tail there is
124
+ return true
125
+ case s .Params .SyncFromHash != nil && bytes .Equal (s .Params .SyncFromHash , tail .Hash ()):
126
+ return true
127
+ case s .Params .SyncFromHeight != 0 && s .Params .SyncFromHeight == tail .Height ():
128
+ return true
129
+ default :
130
+ return false
131
+ }
132
+ }
133
+
59
134
// subjectiveHead returns the latest known local header that is not expired(within trusting period).
60
135
// If the header is expired, it is retrieved from a trusted peer without validation;
61
136
// in other words, an automatic subjective initialization is performed.
@@ -70,15 +145,14 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
70
145
}
71
146
// if pending is empty - get the latest stored/synced head
72
147
storeHead , err := s .store .Head (ctx )
73
- if err != nil {
148
+ switch {
149
+ case errors .Is (err , header .ErrEmptyStore ):
150
+ log .Infow ("no stored head, initializing..." , "height" )
151
+ case ! storeHead .IsZero () && isExpired (storeHead , s .Params .TrustingPeriod ):
152
+ log .Infow ("stored head header expired" , "height" , storeHead .Height ())
153
+ default :
74
154
return storeHead , err
75
155
}
76
- // check if the stored header is not expired and use it
77
- if ! isExpired (storeHead , s .Params .TrustingPeriod ) {
78
- return storeHead , nil
79
- }
80
- // otherwise, request head from a trusted peer
81
- log .Infow ("stored head header expired" , "height" , storeHead .Height ())
82
156
83
157
trustHead , err := s .head .Head (ctx )
84
158
if err != nil {
@@ -257,3 +331,13 @@ func isRecent[H header.Header[H]](header H, blockTime, recencyThreshold time.Dur
257
331
}
258
332
return time .Since (header .Time ()) <= recencyThreshold
259
333
}
334
+
335
+ func estimateTail [H header.Header [H ]](head H , blockTime , trustingPeriod time.Duration ) (height uint64 ) {
336
+ headersToRetain := uint64 (trustingPeriod / blockTime )
337
+
338
+ if headersToRetain >= head .Height () {
339
+ return 1
340
+ }
341
+ tail := head .Height () - headersToRetain
342
+ return tail
343
+ }
0 commit comments