2020-06-04 00:14:36 +00:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-11 22:07:17 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2020-06-04 00:14:36 +00:00
|
|
|
|
2020-09-07 03:49:10 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2020-06-04 00:14:36 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
|
|
lru "github.com/hashicorp/golang-lru"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
)
|
|
|
|
|
2020-08-11 22:07:17 +00:00
|
|
|
var DefaultChainIndexCacheSize = 32 << 10
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
if s := os.Getenv("LOTUS_CHAIN_INDEX_CACHE"); s != "" {
|
|
|
|
lcic, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to parse 'LOTUS_CHAIN_INDEX_CACHE' env var: %s", err)
|
|
|
|
}
|
|
|
|
DefaultChainIndexCacheSize = lcic
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
type ChainIndex struct {
|
|
|
|
skipCache *lru.ARCCache
|
|
|
|
|
|
|
|
loadTipSet loadTipSetFunc
|
|
|
|
|
|
|
|
skipLength abi.ChainEpoch
|
|
|
|
}
|
|
|
|
type loadTipSetFunc func(types.TipSetKey) (*types.TipSet, error)
|
|
|
|
|
|
|
|
func NewChainIndex(lts loadTipSetFunc) *ChainIndex {
|
2020-08-11 22:07:17 +00:00
|
|
|
sc, _ := lru.NewARC(DefaultChainIndexCacheSize)
|
2020-06-04 00:14:36 +00:00
|
|
|
return &ChainIndex{
|
|
|
|
skipCache: sc,
|
|
|
|
loadTipSet: lts,
|
|
|
|
skipLength: 20,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type lbEntry struct {
|
|
|
|
ts *types.TipSet
|
|
|
|
parentHeight abi.ChainEpoch
|
2020-06-04 21:56:57 +00:00
|
|
|
targetHeight abi.ChainEpoch
|
2020-06-04 00:14:36 +00:00
|
|
|
target types.TipSetKey
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
func (ci *ChainIndex) GetTipsetByHeight(_ context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
2020-06-04 00:14:36 +00:00
|
|
|
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
|
2020-06-04 21:56:57 +00:00
|
|
|
} else if to > lbe.targetHeight {
|
2020-06-04 01:25:41 +00:00
|
|
|
return ci.walkBack(lbe.ts, to)
|
2020-06-04 00:14:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cur = lbe.target
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 21:56:57 +00:00
|
|
|
func (ci *ChainIndex) GetTipsetByHeightWithoutCache(from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
|
|
|
return ci.walkBack(from, to)
|
|
|
|
}
|
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) {
|
|
|
|
ts, err := ci.loadTipSet(tsk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-04 01:25:41 +00:00
|
|
|
if ts.Height() == 0 {
|
|
|
|
return &lbEntry{
|
|
|
|
ts: ts,
|
|
|
|
parentHeight: 0,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
rheight -= ci.skipLength
|
|
|
|
|
2020-06-11 05:15:46 +00:00
|
|
|
var skipTarget *types.TipSet
|
|
|
|
if parent.Height() < rheight {
|
|
|
|
skipTarget = parent
|
|
|
|
} else {
|
|
|
|
skipTarget, err = ci.walkBack(parent, rheight)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("fillCache walkback: %w", err)
|
|
|
|
}
|
2020-06-04 00:14:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lbe := &lbEntry{
|
|
|
|
ts: ts,
|
|
|
|
parentHeight: parent.Height(),
|
2020-06-04 21:56:57 +00:00
|
|
|
targetHeight: skipTarget.Height(),
|
2020-06-04 00:14:36 +00:00
|
|
|
target: skipTarget.Key(),
|
|
|
|
}
|
|
|
|
ci.skipCache.Add(tsk, lbe)
|
|
|
|
|
|
|
|
return lbe, nil
|
|
|
|
}
|
|
|
|
|
2020-06-11 12:49:48 +00:00
|
|
|
// floors to nearest skipLength multiple
|
2020-06-04 00:14:36 +00:00
|
|
|
func (ci *ChainIndex) roundHeight(h abi.ChainEpoch) abi.ChainEpoch {
|
2020-06-11 12:49:48 +00:00
|
|
|
return (h / ci.skipLength) * ci.skipLength
|
2020-06-04 00:14:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2020-06-11 12:49:48 +00:00
|
|
|
// in case pts is lower than the epoch we're looking for (null blocks)
|
|
|
|
// return a tipset above that height
|
2020-06-04 00:14:36 +00:00
|
|
|
return ts, nil
|
|
|
|
}
|
|
|
|
if to == pts.Height() {
|
|
|
|
return pts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ts = pts
|
|
|
|
}
|
|
|
|
}
|