lotus/chain/checkpoint.go
Steven Allen 6cbeb9aad6
fix: sync: atomically switch chains when checkpointing (#11595)
We can now atomically switch chains when checkpointing. Previously,
we'd call `SetHead` followed by `SetCheckpoint`. Unfortunately, that's
not atomic and the "head" could have reverted before we called
`SetCheckpoint` (causing the latter to fail).

Now, we just call `SetCheckpoint` and let `SetCheckpoint` adjust our
head. This changes the behavior of `ChainStore.SetCheckpoint`, but
`Syncer.SyncCheckpoint` is the only caller anyways.
2024-01-26 08:50:32 -08:00

44 lines
1.2 KiB
Go

package chain
import (
"context"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/types"
)
func (syncer *Syncer) SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) error {
if tsk == types.EmptyTSK {
return xerrors.Errorf("called with empty tsk")
}
ts, err := syncer.ChainStore().LoadTipSet(ctx, tsk)
if err != nil {
tss, err := syncer.Exchange.GetBlocks(ctx, tsk, 1)
if err != nil {
return xerrors.Errorf("failed to fetch tipset: %w", err)
} else if len(tss) != 1 {
return xerrors.Errorf("expected 1 tipset, got %d", len(tss))
}
ts = tss[0]
}
hts := syncer.ChainStore().GetHeaviestTipSet()
if hts.Equals(ts) {
// Current head, no need to switch.
} else if anc, err := syncer.store.IsAncestorOf(ctx, ts, hts); err != nil {
return xerrors.Errorf("failed to walk the chain when checkpointing: %w", err)
} else if anc {
// New checkpoint is on the current chain, we definitely have the tipsets.
} else if err := syncer.collectChain(ctx, ts, hts, true); err != nil {
return xerrors.Errorf("failed to collect chain for checkpoint: %w", err)
}
if err := syncer.ChainStore().SetCheckpoint(ctx, ts); err != nil {
return xerrors.Errorf("failed to set the chain checkpoint: %w", err)
}
return nil
}