diff --git a/api/api.go b/api/api.go index 13e564818..c787d17c2 100644 --- a/api/api.go +++ b/api/api.go @@ -57,6 +57,7 @@ type FullNode interface { ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error) ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) + ChainSetHead(context.Context, *types.TipSet) error // syncer SyncState(context.Context) (*SyncState, error) diff --git a/api/struct.go b/api/struct.go index ed2e8e933..ab61ef39e 100644 --- a/api/struct.go +++ b/api/struct.go @@ -47,6 +47,7 @@ type FullNodeStruct struct { ChainGetParentMessages func(context.Context, cid.Cid) ([]Message, error) `perm:"read"` ChainGetTipSetByHeight func(context.Context, uint64, *types.TipSet) (*types.TipSet, error) `perm:"read"` ChainReadObj func(context.Context, cid.Cid) ([]byte, error) `perm:"read"` + ChainSetHead func(context.Context, *types.TipSet) error `perm:"admin"` SyncState func(context.Context) (*SyncState, error) `perm:"read"` @@ -303,6 +304,10 @@ func (c *FullNodeStruct) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, return c.Internal.ChainReadObj(ctx, obj) } +func (c *FullNodeStruct) ChainSetHead(ctx context.Context, ts *types.TipSet) error { + return c.Internal.ChainSetHead(ctx, ts) +} + func (c *FullNodeStruct) SyncState(ctx context.Context) (*SyncState, error) { return c.Internal.SyncState(ctx) } diff --git a/chain/store/store.go b/chain/store/store.go index 311f34aa2..abe483785 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -212,31 +212,45 @@ func (cs *ChainStore) MaybeTakeHeavierTipSet(ts *types.TipSet) error { // TODO: don't do this for initial sync. Now that we don't have a // difference between 'bootstrap sync' and 'caught up' sync, we need // some other heuristic. - if cs.heaviest != nil { - revert, apply, err := cs.ReorgOps(cs.heaviest, ts) - if err != nil { - return errors.Wrap(err, "computing reorg ops failed") - } - for _, hcf := range cs.headChangeNotifs { - if err := hcf(revert, apply); err != nil { - return errors.Wrap(err, "head change func errored (BAD)") - } - } - } else { - log.Warn("no heaviest tipset found, using %s", ts.Cids()) - } - - log.Debugf("New heaviest tipset! %s", ts.Cids()) - cs.heaviest = ts - - if err := cs.writeHead(ts); err != nil { - log.Errorf("failed to write chain head: %s", err) - return nil - } + return cs.takeHeaviestTipSet(ts) } return nil } +func (cs *ChainStore) takeHeaviestTipSet(ts *types.TipSet) error { + if cs.heaviest != nil { + revert, apply, err := cs.ReorgOps(cs.heaviest, ts) + if err != nil { + return errors.Wrap(err, "computing reorg ops failed") + } + for _, hcf := range cs.headChangeNotifs { + if err := hcf(revert, apply); err != nil { + return errors.Wrap(err, "head change func errored (BAD)") + } + } + } else { + log.Warn("no heaviest tipset found, using %s", ts.Cids()) + } + + log.Debugf("New heaviest tipset! %s", ts.Cids()) + cs.heaviest = ts + + if err := cs.writeHead(ts); err != nil { + log.Errorf("failed to write chain head: %s", err) + return nil + } + + return nil +} + +// SetHead sets the chainstores current 'best' head node. +// This should only be called if something is broken and needs fixing +func (cs *ChainStore) SetHead(ts *types.TipSet) error { + cs.heaviestLk.Lock() + defer cs.heaviestLk.Unlock() + return cs.takeHeaviestTipSet(ts) +} + func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) { for _, c := range ts.Cids() { has, err := cs.bs.Has(c) diff --git a/cli/chain.go b/cli/chain.go index f4fcab5aa..f88343a1d 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1,6 +1,7 @@ package cli import ( + "context" "encoding/json" "fmt" @@ -204,3 +205,50 @@ var chainGetMsgCmd = &cli.Command{ return nil }, } + +var chainSetHead = &cli.Command{ + Name: "sethead", + Usage: "manually set the local nodes head tipset (Caution: normally only used for recovery)", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if !cctx.Args().Present() { + return fmt.Errorf("must pass cids for tipset to set as head") + } + + ts, err := parseTipSet(api, ctx, cctx.Args().Slice()) + if err != nil { + return err + } + + if err := api.ChainSetHead(ctx, ts); err != nil { + return err + } + + return nil + }, +} + +func parseTipSet(api api.FullNode, ctx context.Context, vals []string) (*types.TipSet, error) { + var headers []*types.BlockHeader + for _, c := range vals { + blkc, err := cid.Decode(c) + if err != nil { + return nil, err + } + + bh, err := api.ChainGetBlock(ctx, blkc) + if err != nil { + return nil, err + } + + headers = append(headers, bh) + } + + return types.NewTipSet(headers) +} diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index 927053b8e..0d68d22cb 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -157,3 +157,7 @@ func (a *ChainAPI) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error return blk.RawData(), nil } + +func (a *ChainAPI) ChainSetHead(ctx context.Context, ts *types.TipSet) error { + return a.Chain.SetHead(ts) +}