From c01f70105fda789a060343cf0eb4541e424f09bd Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 3 Jun 2020 17:14:36 -0700 Subject: [PATCH 1/3] implement chain index to make lookbacks faster --- chain/store/index.go | 144 +++++++++++++++++++++++++++++++++++++++++++ chain/store/store.go | 32 +++++----- 2 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 chain/store/index.go diff --git a/chain/store/index.go b/chain/store/index.go new file mode 100644 index 000000000..36096e58a --- /dev/null +++ b/chain/store/index.go @@ -0,0 +1,144 @@ +package store + +import ( + "context" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/actors/abi" + lru "github.com/hashicorp/golang-lru" + "golang.org/x/xerrors" +) + +type ChainIndex struct { + skipCache *lru.ARCCache + + loadTipSet loadTipSetFunc + + skipLength abi.ChainEpoch +} +type loadTipSetFunc func(types.TipSetKey) (*types.TipSet, error) + +func NewChainIndex(lts loadTipSetFunc) *ChainIndex { + sc, _ := lru.NewARC(8192) + return &ChainIndex{ + skipCache: sc, + loadTipSet: lts, + skipLength: 20, + } +} + +type lbEntry struct { + ts *types.TipSet + parentHeight abi.ChainEpoch + target types.TipSetKey +} + +func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + if from.Height()-to <= ci.skipLength { + return ci.walkBack(from, to) + } + + rounded, err := ci.roundDown(from) + if err != nil { + return nil, err + } + + cur := rounded.Key() + for { + cval, ok := ci.skipCache.Get(cur) + if !ok { + fc, err := ci.fillCache(cur) + if err != nil { + return nil, err + } + cval = fc + } + + lbe := cval.(*lbEntry) + if lbe.ts.Height() == to || lbe.parentHeight < to { + return lbe.ts, nil + } else if to > lbe.ts.Height()-ci.skipLength { + return ci.walkBack(from, to) + } + + cur = lbe.target + } +} + +func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) { + ts, err := ci.loadTipSet(tsk) + if err != nil { + return nil, err + } + + // will either be equal to ts.Height, or at least > ts.Parent.Height() + rheight := ci.roundHeight(ts.Height()) + + parent, err := ci.loadTipSet(ts.Parents()) + if err != nil { + return nil, err + } + + if parent.Height() > rheight { + return nil, xerrors.Errorf("cache is inconsistent") + } + + rheight -= ci.skipLength + + skipTarget, err := ci.walkBack(parent, rheight) + if err != nil { + return nil, err + } + + lbe := &lbEntry{ + ts: ts, + parentHeight: parent.Height(), + target: skipTarget.Key(), + } + ci.skipCache.Add(tsk, lbe) + + return lbe, nil +} + +func (ci *ChainIndex) roundHeight(h abi.ChainEpoch) abi.ChainEpoch { + return abi.ChainEpoch(h/ci.skipLength) * ci.skipLength +} + +func (ci *ChainIndex) roundDown(ts *types.TipSet) (*types.TipSet, error) { + target := ci.roundHeight(ts.Height()) + + rounded, err := ci.walkBack(ts, target) + if err != nil { + return nil, err + } + + return rounded, nil +} + +func (ci *ChainIndex) walkBack(from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + if to > from.Height() { + return nil, xerrors.Errorf("looking for tipset with height greater than start point") + } + + if to == from.Height() { + return from, nil + } + + ts := from + + for { + pts, err := ci.loadTipSet(ts.Parents()) + if err != nil { + return nil, err + } + + if to > pts.Height() { + return ts, nil + } + if to == pts.Height() { + return pts, nil + } + + ts = pts + } +} diff --git a/chain/store/store.go b/chain/store/store.go index 1d0668032..f5a6ee0f6 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -62,6 +62,8 @@ type ChainStore struct { tstLk sync.Mutex tipsets map[abi.ChainEpoch][]cid.Cid + cindex *ChainIndex + reorgCh chan<- reorg headChangeNotifs []func(rev, app []*types.TipSet) error @@ -84,6 +86,10 @@ func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Sys vmcalls: vmcalls, } + ci := NewChainIndex(cs.LoadTipSet) + + cs.cindex = ci + cs.reorgCh = cs.reorgWorker(context.TODO()) hcnf := func(rev, app []*types.TipSet) error { @@ -951,24 +957,16 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t log.Warnf("expensive call to GetTipsetByHeight, seeking %d levels", ts.Height()-h) } - for { - pts, err := cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, err - } - - if h > pts.Height() { - if prev { - return pts, nil - } - return ts, nil - } - if h == pts.Height() { - return pts, nil - } - - ts = pts + lbts, err := cs.cindex.GetTipsetByHeight(ctx, ts, h) + if err != nil { + return nil, err } + + if lbts.Height() == h || !prev { + return lbts, nil + } + + return cs.LoadTipSet(lbts.Parents()) } func recurseLinks(bs blockstore.Blockstore, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { From 86083531c62e1fb93346a7582d45dbf4597e5494 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 3 Jun 2020 18:25:41 -0700 Subject: [PATCH 2/3] a couple bugfixes --- chain/store/index.go | 9 ++++++++- chain/store/store.go | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/chain/store/index.go b/chain/store/index.go index 36096e58a..7d642eb10 100644 --- a/chain/store/index.go +++ b/chain/store/index.go @@ -58,7 +58,7 @@ func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, if lbe.ts.Height() == to || lbe.parentHeight < to { return lbe.ts, nil } else if to > lbe.ts.Height()-ci.skipLength { - return ci.walkBack(from, to) + return ci.walkBack(lbe.ts, to) } cur = lbe.target @@ -71,6 +71,13 @@ func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) { return nil, err } + if ts.Height() == 0 { + return &lbEntry{ + ts: ts, + parentHeight: 0, + }, nil + } + // will either be equal to ts.Height, or at least > ts.Parent.Height() rheight := ci.roundHeight(ts.Height()) diff --git a/chain/store/store.go b/chain/store/store.go index f5a6ee0f6..dcd1a5ead 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -946,7 +946,7 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t } if h > ts.Height() { - return nil, xerrors.Errorf("looking for tipset with height less than start point") + return nil, xerrors.Errorf("looking for tipset with height greater than start point") } if h == ts.Height() { From 656b285195008bcd047b90e983c38fa0efed2df5 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 4 Jun 2020 17:56:57 -0400 Subject: [PATCH 3/3] Bugfix: Begin walkback when lookback target's height is too low --- chain/store/index.go | 8 +++++++- chain/store/store.go | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/chain/store/index.go b/chain/store/index.go index 7d642eb10..15d5d7025 100644 --- a/chain/store/index.go +++ b/chain/store/index.go @@ -30,6 +30,7 @@ func NewChainIndex(lts loadTipSetFunc) *ChainIndex { type lbEntry struct { ts *types.TipSet parentHeight abi.ChainEpoch + targetHeight abi.ChainEpoch target types.TipSetKey } @@ -57,7 +58,7 @@ func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, lbe := cval.(*lbEntry) if lbe.ts.Height() == to || lbe.parentHeight < to { return lbe.ts, nil - } else if to > lbe.ts.Height()-ci.skipLength { + } else if to > lbe.targetHeight { return ci.walkBack(lbe.ts, to) } @@ -65,6 +66,10 @@ func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, } } +func (ci *ChainIndex) GetTipsetByHeightWithoutCache(from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + return ci.walkBack(from, to) +} + func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) { ts, err := ci.loadTipSet(tsk) if err != nil { @@ -100,6 +105,7 @@ func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) { lbe := &lbEntry{ ts: ts, parentHeight: parent.Height(), + targetHeight: skipTarget.Height(), target: skipTarget.Key(), } ci.skipCache.Add(tsk, lbe) diff --git a/chain/store/store.go b/chain/store/store.go index dcd1a5ead..94d94da79 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -962,6 +962,14 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return nil, err } + if lbts.Height() < h { + log.Warnf("chain index returned the wrong tipset at height %d, using slow retrieval", h) + lbts, err = cs.cindex.GetTipsetByHeightWithoutCache(ts, h) + if err != nil { + return nil, err + } + } + if lbts.Height() == h || !prev { return lbts, nil }