From 6610a247cfebb4a213e92fc2bb85dcd23e87c189 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 9 Oct 2020 22:41:34 +0200 Subject: [PATCH 1/3] Dump the block validation cache whenever we perform an import This solves a problem with folks resurrecting long-out-of-sync nodes via snapshot imports. The interface switch to Batching is necessary: startup is too long otherwise ( 8 minutes just to clear everything on a relatively old node ) --- chain/store/store.go | 42 +++++++++++++++++++++++++++++++++++++++++- cmd/lotus/daemon.go | 6 +++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/chain/store/store.go b/chain/store/store.go index 39f3d5c48..22b305afb 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -8,6 +8,7 @@ import ( "io" "os" "strconv" + "strings" "sync" "golang.org/x/sync/errgroup" @@ -38,7 +39,9 @@ import ( lru "github.com/hashicorp/golang-lru" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" dstore "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/query" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" car "github.com/ipld/go-car" @@ -102,7 +105,7 @@ type HeadChangeEvt struct { // 2. a block => messages references cache. type ChainStore struct { bs bstore.Blockstore - ds dstore.Datastore + ds dstore.Batching heaviestLk sync.Mutex heaviest *types.TipSet @@ -446,6 +449,43 @@ func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) return nil } +// FlushValidationCache removes all results of block validation from the +// chain metadata store. Usually the first step after a new chain import. +func (cs *ChainStore) FlushValidationCache() error { + log.Infof("clearing block validation cache...") + + dsWalk, err := cs.ds.Query(query.Query{KeysOnly: true}) + if err != nil { + return xerrors.Errorf("failed to initialize key listing query: %w", err) + } + + allKeys, err := dsWalk.Rest() + if err != nil { + return xerrors.Errorf("failed to run key listing query: %w", err) + } + + batch, err := cs.ds.Batch() + if err != nil { + return xerrors.Errorf("failed to open a DS batch: %w", err) + } + + delCnt := 0 + for _, k := range allKeys { + if strings.HasPrefix(k.Key, blockValidationCacheKeyPrefix.String()) { + delCnt++ + batch.Delete(datastore.RawKey(k.Key)) + } + } + + if err := batch.Commit(); err != nil { + return xerrors.Errorf("failed to commit the DS batch: %w", err) + } + + log.Infof("%d block validation entries cleared.", delCnt) + + 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 { diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index a1f301572..4ff63be11 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -436,6 +436,10 @@ func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { return xerrors.Errorf("importing chain failed: %w", err) } + if err := cst.FlushValidationCache(); err != nil { + return xerrors.Errorf("flushing validation cache failed: %w", err) + } + gb, err := cst.GetTipsetByHeight(context.TODO(), 0, ts, true) if err != nil { return err @@ -455,7 +459,7 @@ func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { } } - log.Info("accepting %s as new head", ts.Cids()) + log.Infof("accepting %s as new head", ts.Cids()) if err := cst.SetHead(ts); err != nil { return err } From 85abca4b16231c7693b7b3ca142113e737c19240 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Sat, 10 Oct 2020 04:00:12 +0200 Subject: [PATCH 2/3] Delint --- chain/store/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/store/store.go b/chain/store/store.go index 22b305afb..6fb689b3e 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -473,7 +473,7 @@ func (cs *ChainStore) FlushValidationCache() error { for _, k := range allKeys { if strings.HasPrefix(k.Key, blockValidationCacheKeyPrefix.String()) { delCnt++ - batch.Delete(datastore.RawKey(k.Key)) + batch.Delete(datastore.RawKey(k.Key)) // nolint:errcheck } } From 23f5a99117226fba70fea235c421f1d7b6f26745 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Sat, 10 Oct 2020 15:36:32 +0200 Subject: [PATCH 3/3] Add comment clarifying current codepath --- chain/store/store.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/chain/store/store.go b/chain/store/store.go index 6fb689b3e..c6fc0cbef 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -454,7 +454,17 @@ func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) func (cs *ChainStore) FlushValidationCache() error { log.Infof("clearing block validation cache...") - dsWalk, err := cs.ds.Query(query.Query{KeysOnly: true}) + dsWalk, err := cs.ds.Query(query.Query{ + // Potential TODO: the validation cache is not a namespace on its own + // but is rather constructed as prefixed-key `foo:bar` via .Instance(), which + // in turn does not work with the filter, which can match only on `foo/bar` + // + // If this is addressed (blockcache goes into its own sub-namespace) then + // strings.HasPrefix(...) below can be skipped + // + //Prefix: blockValidationCacheKeyPrefix.String() + KeysOnly: true, + }) if err != nil { return xerrors.Errorf("failed to initialize key listing query: %w", err) }