@@ -13,7 +13,8 @@ import (
13
13
// the exchange to request the head of the chain from the network.
14
14
var headRequestTimeout = time .Second * 2
15
15
16
- // Head returns the Network Head.
16
+ // Head returns the Network Head or an error. It will try to get the most recent header until it fails entirely.
17
+ // It may return an error with a header which caused it.
17
18
//
18
19
// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime)
19
20
// Otherwise, we attempt to request recent network head from a trusted peer and
@@ -25,6 +26,13 @@ func (s *Syncer[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, err
25
26
if err != nil {
26
27
return sbjHead , err
27
28
}
29
+ defer func () {
30
+ // always ensure tail is up to date
31
+ _ , err = s .subjectiveTail (ctx , sbjHead )
32
+ if err != nil {
33
+ log .Errorw ("subjective tail" , "head_height" , sbjHead .Height (), "err" , err )
34
+ }
35
+ }()
28
36
// if subjective header is recent enough (relative to the network's block time) - just use it
29
37
if isRecent (sbjHead , s .Params .blockTime , s .Params .recencyThreshold ) {
30
38
return sbjHead , nil
@@ -59,7 +67,7 @@ func (s *Syncer[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, err
59
67
// subjectiveHead returns the latest known local header that is not expired(within trusting period).
60
68
// If the header is expired, it is retrieved from a trusted peer without validation;
61
69
// in other words, an automatic subjective initialization is performed.
62
- func (s * Syncer [H ]) subjectiveHead (ctx context.Context ) (H , error ) {
70
+ func (s * Syncer [H ]) subjectiveHead (ctx context.Context ) (sbjHead H , err error ) {
63
71
// pending head is the latest known subjective head and sync target, so try to get it
64
72
// NOTES:
65
73
// * Empty when no sync is in progress
@@ -70,37 +78,53 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
70
78
}
71
79
// if pending is empty - get the latest stored/synced head
72
80
storeHead , err := s .store .Head (ctx )
73
- if err != nil {
81
+ switch {
82
+ case errors .Is (err , header .ErrEmptyStore ):
83
+ log .Info ("empty store, initializing..." )
84
+ s .metrics .subjectiveInitialization (s .ctx )
85
+ case ! storeHead .IsZero () && isExpired (storeHead , s .Params .TrustingPeriod ):
86
+ log .Infow ("stored head header expired" , "height" , storeHead .Height ())
87
+ default :
74
88
return storeHead , err
75
89
}
76
- // check if the stored header is not expired and use it
77
- if ! isExpired (storeHead , s .Params .TrustingPeriod ) {
78
- return storeHead , nil
90
+ // fetch a new head from trusted peers if not available locally
91
+ newHead , err := s .head .Head (ctx )
92
+ if err != nil {
93
+ return newHead , err
94
+ }
95
+ switch {
96
+ case isExpired (newHead , s .Params .TrustingPeriod ):
97
+ // forbid initializing off an expired header
98
+ err := fmt .Errorf ("subjective initialization with an expired header(%d)" , newHead .Height ())
99
+ log .Error (err , "\n trusted peers are out of sync" )
100
+ s .metrics .trustedPeersOutOufSync (s .ctx )
101
+ return newHead , err
102
+ case ! isRecent (newHead , s .Params .blockTime , s .Params .recencyThreshold ):
103
+ // it's not the most recent, buts its good enough - allow initialization
104
+ log .Warnw ("subjective initialization with not recent header" , "height" , newHead .Height ())
105
+ s .metrics .trustedPeersOutOufSync (s .ctx )
79
106
}
80
- // otherwise, request head from a trusted peer
81
- log .Infow ("stored head header expired" , "height" , storeHead .Height ())
82
107
83
- trustHead , err : = s .head . Head (ctx )
108
+ _ , err = s .subjectiveTail (ctx , newHead )
84
109
if err != nil {
85
- return trustHead , err
110
+ return newHead , fmt .Errorf (
111
+ "subjective tail during subjective initialization for head %d: %w" ,
112
+ newHead .Height (),
113
+ err ,
114
+ )
86
115
}
87
- s . metrics . subjectiveInitialization ( s . ctx )
88
- // and set it as the new subjective head without validation,
116
+
117
+ // and set the fetched head as the new subjective head validating it against the tail
89
118
// or, in other words, do 'automatic subjective initialization'
90
- // NOTE: we avoid validation as the head expired to prevent possibility of the Long-Range Attack
91
- s .setSubjectiveHead (ctx , trustHead )
92
- switch {
93
- default :
94
- log .Infow ("subjective initialization finished" , "height" , trustHead .Height ())
95
- return trustHead , nil
96
- case isExpired (trustHead , s .Params .TrustingPeriod ):
97
- log .Warnw ("subjective initialization with an expired header" , "height" , trustHead .Height ())
98
- case ! isRecent (trustHead , s .Params .blockTime , s .Params .recencyThreshold ):
99
- log .Warnw ("subjective initialization with an old header" , "height" , trustHead .Height ())
119
+ err = s .incomingNetworkHead (ctx , newHead )
120
+ if err != nil {
121
+ err = fmt .Errorf ("subjective initialization failed for head(%d): %w" , newHead .Height (), err )
122
+ log .Error (err )
123
+ return newHead , err
100
124
}
101
- log . Warn ( "trusted peer is out of sync" )
102
- s . metrics . trustedPeersOutOufSync ( s . ctx )
103
- return trustHead , nil
125
+
126
+ log . Infow ( "subjective initialization finished" , "head" , newHead . Height () )
127
+ return newHead , nil
104
128
}
105
129
106
130
// setSubjectiveHead takes already validated head and sets it as the new sync target.
@@ -138,20 +162,19 @@ func (s *Syncer[H]) incomingNetworkHead(ctx context.Context, head H) error {
138
162
s .incomingMu .Lock ()
139
163
defer s .incomingMu .Unlock ()
140
164
141
- err := s .verify (ctx , head )
142
- if err != nil {
165
+ if err := s .verify (ctx , head ); err != nil {
143
166
return err
144
167
}
145
168
146
169
s .setSubjectiveHead (ctx , head )
147
- return err
170
+ return nil
148
171
}
149
172
150
173
// verify verifies given network head candidate.
151
174
func (s * Syncer [H ]) verify (ctx context.Context , newHead H ) error {
152
175
sbjHead , err := s .subjectiveHead (ctx )
153
176
if err != nil {
154
- log .Errorw ("getting subjective head during validation " , "err" , err )
177
+ log .Errorw ("getting subjective head during new network head verification " , "err" , err )
155
178
return err
156
179
}
157
180
0 commit comments