6060 ErrNewChainTooLong = errors .New ("input chain forked from best chain past finality limit" )
6161 // ErrUnexpectedStoreState indicates that the syncer's chain bsstore is violating expected invariants.
6262 ErrUnexpectedStoreState = errors .New ("the chain bsstore is in an unexpected state" )
63+ ErrForkCheckpoint = fmt .Errorf ("fork would require us to diverge from checkpointed block" )
6364
6465 logSyncer = logging .Logger ("chainsync.syncer" )
6566)
@@ -137,8 +138,7 @@ type Syncer struct {
137138
138139 clock clock.Clock
139140
140- bsstore blockstoreutil.Blockstore
141- checkPoint types.TipSetKey
141+ bsstore blockstoreutil.Blockstore
142142
143143 fork fork.IFork
144144
@@ -199,45 +199,41 @@ func (syncer *Syncer) syncOne(ctx context.Context, parent, next *types.TipSet) e
199199 stopwatch := syncOneTimer .Start ()
200200 defer stopwatch (ctx )
201201
202- var err error
203-
204- if ! parent .Key ().Equals (syncer .checkPoint ) {
205- var wg errgroup.Group
206- for i := 0 ; i < next .Len (); i ++ {
207- blk := next .At (i )
208- wg .Go (func () error {
209- // Fetch the URL.
210- err := syncer .blockValidator .ValidateFullBlock (ctx , blk )
211- if err == nil {
212- if err := syncer .chainStore .AddToTipSetTracker (ctx , blk ); err != nil {
213- return fmt .Errorf ("failed to add validated header to tipset tracker: %w" , err )
214- }
202+ var wg errgroup.Group
203+ for i := 0 ; i < next .Len (); i ++ {
204+ blk := next .At (i )
205+ wg .Go (func () error {
206+ // Fetch the URL.
207+ err := syncer .blockValidator .ValidateFullBlock (ctx , blk )
208+ if err == nil {
209+ if err := syncer .chainStore .AddToTipSetTracker (ctx , blk ); err != nil {
210+ return fmt .Errorf ("failed to add validated header to tipset tracker: %w" , err )
215211 }
216- return err
217- })
218- }
219- err = wg .Wait ()
220- if err != nil {
221- var rootNotMatch bool // nolint
222-
223- if merr , isok := err .(* multierror.Error ); isok {
224- for _ , e := range merr .Errors {
225- if isRootNotMatch (e ) {
226- rootNotMatch = true
227- break
228- }
229- }
230- } else {
231- rootNotMatch = isRootNotMatch (err ) // nolint
232212 }
213+ return err
214+ })
215+ }
216+ err := wg .Wait ()
217+ if err != nil {
218+ var rootNotMatch bool // nolint
233219
234- if rootNotMatch { // nolint
235- // todo: should here rollback, and re-compute?
236- _ = syncer .stmgr .Rollback (ctx , parent , next )
220+ if merr , isok := err .(* multierror.Error ); isok {
221+ for _ , e := range merr .Errors {
222+ if isRootNotMatch (e ) {
223+ rootNotMatch = true
224+ break
225+ }
237226 }
227+ } else {
228+ rootNotMatch = isRootNotMatch (err ) // nolint
229+ }
238230
239- return fmt .Errorf ("validate mining failed %w" , err )
231+ if rootNotMatch { // nolint
232+ // todo: should here rollback, and re-compute?
233+ _ = syncer .stmgr .Rollback (ctx , parent , next )
240234 }
235+
236+ return fmt .Errorf ("validate mining failed %w" , err )
241237 }
242238
243239 syncer .chainStore .PersistTipSetKey (ctx , next .Key ())
@@ -297,8 +293,25 @@ func (syncer *Syncer) HandleNewTipSet(ctx context.Context, target *syncTypes.Tar
297293 return errors .New ("do not sync to a target has synced before" )
298294 }
299295
296+ if target .Head .Height () == head .Height () {
297+ // check if maybeHead is fully contained in headTipSet
298+ // meaning we already synced all the blocks that are a part of maybeHead
299+ // if that is the case, there is nothing for us to do
300+ // we need to exit out early, otherwise checkpoint-fork logic might wrongly reject it
301+ fullyContained := true
302+ for _ , c := range target .Head .Cids () {
303+ if ! head .Contains (c ) {
304+ fullyContained = false
305+ break
306+ }
307+ }
308+ if fullyContained {
309+ return nil
310+ }
311+ }
312+
300313 syncer .exchangeClient .AddPeer (target .Sender )
301- tipsets , err := syncer .fetchChainBlocks (ctx , head , target .Head )
314+ tipsets , err := syncer .fetchChainBlocks (ctx , head , target .Head , false )
302315 if err != nil {
303316 return errors .Wrapf (err , "failure fetching or validating headers" )
304317 }
@@ -346,7 +359,7 @@ func (syncer *Syncer) syncSegement(ctx context.Context, target *syncTypes.Target
346359 errProcessChan <- processErr
347360 return
348361 }
349- if ! parent .Key ().Equals (syncer .checkPoint ) {
362+ if ! parent .Key ().Equals (syncer .chainStore . GetCheckPoint (). Key () ) {
350363 logSyncer .Debugf ("set chain head, height:%d, blocks:%d" , parent .Height (), parent .Len ())
351364 if err := syncer .chainStore .RefreshHeaviestTipSet (ctx , parent .Height ()); err != nil {
352365 errProcessChan <- err
@@ -374,7 +387,7 @@ func (syncer *Syncer) syncSegement(ctx context.Context, target *syncTypes.Target
374387// if local db not exist, get block from network(libp2p),
375388// if there is a fork, get the common root tipset of knowntip and targettip, and return the block data from root tipset to targettip
376389// local(···->A->B) + incoming(C->D->E) => ···->A->B->C->D->E
377- func (syncer * Syncer ) fetchChainBlocks (ctx context.Context , knownTip * types.TipSet , targetTip * types.TipSet ) ([]* types.TipSet , error ) {
390+ func (syncer * Syncer ) fetchChainBlocks (ctx context.Context , knownTip * types.TipSet , targetTip * types.TipSet , ignoreCheckpoint bool ) ([]* types.TipSet , error ) {
378391 chainTipsets := []* types.TipSet {targetTip }
379392 flushDB := func (saveTips []* types.TipSet ) error {
380393 bs := blockstoreutil .NewTemporary ()
@@ -448,6 +461,13 @@ loop:
448461 if err != nil {
449462 return nil , fmt .Errorf ("failed to load next local tipset: %w" , err )
450463 }
464+
465+ if ! ignoreCheckpoint {
466+ if chkpt := syncer .chainStore .GetCheckPoint (); chkpt != nil && base .Height () <= chkpt .Height () {
467+ return nil , fmt .Errorf ("merge point affecting the checkpoing: %w" , ErrForkCheckpoint )
468+ }
469+ }
470+
451471 if base .IsChildOf (knownParent ) {
452472 // common case: receiving a block thats potentially part of the same tipset as our best block
453473 chain .Reverse (chainTipsets )
@@ -456,7 +476,7 @@ loop:
456476
457477 logSyncer .Warnf ("(fork detected) synced header chain, base: %v(%d), knownTip: %v(%d)" , base .Key (), base .Height (),
458478 knownTip .Key (), knownTip .Height ())
459- fork , err := syncer .syncFork (ctx , base , knownTip )
479+ fork , err := syncer .syncFork (ctx , base , knownTip , ignoreCheckpoint )
460480 if err != nil {
461481 if errors .Is (err , ErrForkTooLong ) {
462482 // TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish?
@@ -486,7 +506,15 @@ loop:
486506// D->E-F(targetTip)
487507// A => D->E>F
488508// B-C(knownTip)
489- func (syncer * Syncer ) syncFork (ctx context.Context , incoming * types.TipSet , known * types.TipSet ) ([]* types.TipSet , error ) {
509+ func (syncer * Syncer ) syncFork (ctx context.Context , incoming * types.TipSet , known * types.TipSet , ignoreCheckpoint bool ) ([]* types.TipSet , error ) {
510+ var chkpt * types.TipSet
511+ if ! ignoreCheckpoint {
512+ chkpt = syncer .chainStore .GetCheckPoint ()
513+ if known .Equals (chkpt ) {
514+ return nil , ErrForkCheckpoint
515+ }
516+ }
517+
490518 incomingParentsTsk := incoming .Parents ()
491519 commonParent := false
492520 for _ , incomingParent := range incomingParentsTsk .Cids () {
@@ -701,7 +729,7 @@ func (syncer *Syncer) SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) e
701729 if anc , err := syncer .chainStore .IsAncestorOf (ctx , ts , head ); err != nil {
702730 return fmt .Errorf ("failed to walk the chain when checkpointing: %w" , err )
703731 } else if ! anc {
704- tipsets , err := syncer .fetchChainBlocks (ctx , head , target .Head )
732+ tipsets , err := syncer .fetchChainBlocks (ctx , head , target .Head , true )
705733 if err != nil {
706734 return errors .Wrapf (err , "failure fetching or validating headers" )
707735 }
0 commit comments