Skip to content

Commit 32a4727

Browse files
authored
Merge pull request #6435 from filecoin-project/fix/sync/chck-expansion
fix(sync): do not allow to expand checkpointed tipsets
2 parents 09eda5b + e542184 commit 32a4727

File tree

5 files changed

+93
-56
lines changed

5 files changed

+93
-56
lines changed

app/submodule/chain/chain_submodule.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"github.com/filecoin-project/venus/pkg/vmsupport"
2222
v0api "github.com/filecoin-project/venus/venus-shared/api/chain/v0"
2323
v1api "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
24-
"github.com/filecoin-project/venus/venus-shared/types"
2524
)
2625

2726
// ChainSubmodule enhances the `Node` with chain capabilities.
@@ -33,8 +32,7 @@ type ChainSubmodule struct { //nolint
3332
SystemCall vm.SyscallsImpl
3433
CirculatingSupplyCalculator *chain.CirculatingSupplyCalculator
3534

36-
CheckPoint types.TipSetKey
37-
Drand beacon.Schedule
35+
Drand beacon.Schedule
3836

3937
config chainConfig
4038

@@ -92,7 +90,6 @@ func NewChainSubmodule(ctx context.Context,
9290
Drand: drand,
9391
config: config,
9492
Waiter: waiter,
95-
CheckPoint: chainStore.GetCheckPoint(),
9693
}
9794
err = store.ChainReader.Load(context.TODO())
9895
if err != nil {

pkg/chain/store.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ type Store struct {
100100
// head is the tipset at the head of the best known chain.
101101
head *types.TipSet
102102

103-
checkPoint types.TipSetKey
103+
checkPoint *types.TipSet
104104
// Protects head and genesisCid.
105105
mu sync.RWMutex
106106

@@ -143,7 +143,6 @@ func NewStore(chainDs repo.Datastore,
143143
bsstore: bsstore,
144144
headEvents: pubsub.New(64),
145145

146-
checkPoint: types.EmptyTSK,
147146
genesis: genesisCid,
148147
reorgNotifeeCh: make(chan ReorgNotifee),
149148
tsCache: tsCache,
@@ -156,11 +155,22 @@ func NewStore(chainDs repo.Datastore,
156155

157156
val, err := store.ds.Get(context.TODO(), CheckPoint)
158157
if err != nil {
159-
store.checkPoint = types.NewTipSetKey(genesisCid)
158+
store.checkPoint, err = store.GetTipSet(context.TODO(), types.NewTipSetKey(genesisCid))
159+
if err != nil {
160+
panic(fmt.Errorf("cannot get genesis tipset: %w", err))
161+
}
160162
} else {
161-
_ = store.checkPoint.UnmarshalCBOR(bytes.NewReader(val)) //nolint:staticcheck
163+
var checkPointTSK types.TipSetKey
164+
err := checkPointTSK.UnmarshalCBOR(bytes.NewReader(val))
165+
if err != nil {
166+
panic(fmt.Errorf("cannot unmarshal checkpoint %s: %w", string(val), err))
167+
}
168+
store.checkPoint, err = store.GetTipSet(context.TODO(), checkPointTSK)
169+
if err != nil {
170+
panic(fmt.Errorf("cannot get checkpoint tipset: %w", err))
171+
}
162172
}
163-
log.Infof("check point value: %v", store.checkPoint)
173+
log.Infof("load check point height: %d, key: %v", store.checkPoint.Height(), store.checkPoint.Key())
164174

165175
store.reorgCh = store.reorgWorker(context.TODO())
166176
return store
@@ -1112,8 +1122,8 @@ func (store *Store) SetCheckpoint(ctx context.Context, ts *types.TipSet) error {
11121122
return err
11131123
}
11141124

1115-
store.mu.RLock()
1116-
defer store.mu.RUnlock()
1125+
store.mu.Lock()
1126+
defer store.mu.Unlock()
11171127

11181128
finality := store.head.Height() - policy.ChainFinality
11191129
targetChain, currentChain := ts, store.head
@@ -1167,7 +1177,7 @@ func (store *Store) SetCheckpoint(ctx context.Context, ts *types.TipSet) error {
11671177
if err := store.ds.Put(ctx, CheckPoint, buf.Bytes()); err != nil {
11681178
return fmt.Errorf("checkpoint failed: failed to record checkpoint in the datastore: %w", err)
11691179
}
1170-
store.checkPoint = ts.Key()
1180+
store.checkPoint = ts
11711181

11721182
return nil
11731183
}
@@ -1187,7 +1197,7 @@ func (store *Store) IsAncestorOf(ctx context.Context, a, b *types.TipSet) (bool,
11871197
}
11881198

11891199
// GetCheckPoint get the check point from store or disk.
1190-
func (store *Store) GetCheckPoint() types.TipSetKey {
1200+
func (store *Store) GetCheckPoint() *types.TipSet {
11911201
store.mu.RLock()
11921202
defer store.mu.RUnlock()
11931203

@@ -1722,7 +1732,7 @@ func (store *Store) exceedsForkLength(ctx context.Context, synced, external *typ
17221732
}
17231733

17241734
// Now check to see if we've walked back to the checkpoint.
1725-
if synced.Key().Equals(store.checkPoint) {
1735+
if synced.Key().Equals(store.checkPoint.Key()) {
17261736
return true, nil
17271737
}
17281738

pkg/chain/store_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ func (cbor *CborBlockStore) PutBlocks(ctx context.Context, blocks []*types.Block
4343
func newChainStore(r repo.Repo, genTS *types.TipSet) *CborBlockStore {
4444
tempBlock := r.Datastore()
4545
cborStore := cbor.NewCborStore(tempBlock)
46+
blkBytes, _ := genTS.Blocks()[0].ToStorageBlock()
47+
_ = tempBlock.Put(context.Background(), blkBytes)
4648
return &CborBlockStore{
4749
Store: chain.NewStore(r.ChainDatastore(), tempBlock, genTS.At(0).Cid(), chainselector.Weight),
4850
cborStore: cborStore,

pkg/chainsync/syncer/syncer.go

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ var (
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

Comments
 (0)