@@ -13,7 +13,8 @@ import (
1313// the exchange to request the head of the chain from the network.
1414var headRequestTimeout = time .Second * 2
1515
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.
1718//
1819// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime)
1920// 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
2526 if err != nil {
2627 return sbjHead , err
2728 }
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+ }()
2836 // if subjective header is recent enough (relative to the network's block time) - just use it
2937 if isRecent (sbjHead , s .Params .blockTime , s .Params .recencyThreshold ) {
3038 return sbjHead , nil
@@ -59,7 +67,7 @@ func (s *Syncer[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, err
5967// subjectiveHead returns the latest known local header that is not expired(within trusting period).
6068// If the header is expired, it is retrieved from a trusted peer without validation;
6169// 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 ) {
6371 // pending head is the latest known subjective head and sync target, so try to get it
6472 // NOTES:
6573 // * Empty when no sync is in progress
@@ -70,37 +78,53 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) {
7078 }
7179 // if pending is empty - get the latest stored/synced head
7280 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 :
7488 return storeHead , err
7589 }
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 )
79106 }
80- // otherwise, request head from a trusted peer
81- log .Infow ("stored head header expired" , "height" , storeHead .Height ())
82107
83- trustHead , err : = s .head . Head (ctx )
108+ _ , err = s .subjectiveTail (ctx , newHead )
84109 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+ )
86115 }
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
89118 // 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
100124 }
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
104128}
105129
106130// 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 {
138162 s .incomingMu .Lock ()
139163 defer s .incomingMu .Unlock ()
140164
141- err := s .verify (ctx , head )
142- if err != nil {
165+ if err := s .verify (ctx , head ); err != nil {
143166 return err
144167 }
145168
146169 s .setSubjectiveHead (ctx , head )
147- return err
170+ return nil
148171}
149172
150173// verify verifies given network head candidate.
151174func (s * Syncer [H ]) verify (ctx context.Context , newHead H ) error {
152175 sbjHead , err := s .subjectiveHead (ctx )
153176 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 )
155178 return err
156179 }
157180
0 commit comments