2019-07-26 04:54:22 +00:00
|
|
|
package store
|
2019-07-05 14:29:17 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-07-17 03:05:55 +00:00
|
|
|
"encoding/json"
|
2020-09-08 06:16:34 +00:00
|
|
|
"errors"
|
2020-04-20 17:43:02 +00:00
|
|
|
"os"
|
2020-08-11 22:07:17 +00:00
|
|
|
"strconv"
|
2020-10-09 20:41:34 +00:00
|
|
|
"strings"
|
2019-07-05 14:29:17 +00:00
|
|
|
"sync"
|
2021-06-18 17:15:08 +00:00
|
|
|
"time"
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2020-09-28 13:56:44 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2019-12-19 20:13:17 +00:00
|
|
|
"github.com/filecoin-project/go-address"
|
2020-09-07 03:49:10 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2020-09-28 21:25:58 +00:00
|
|
|
|
2020-04-23 22:15:00 +00:00
|
|
|
"github.com/filecoin-project/lotus/api"
|
2021-01-29 20:01:00 +00:00
|
|
|
bstore "github.com/filecoin-project/lotus/blockstore"
|
2020-09-25 20:08:28 +00:00
|
|
|
"github.com/filecoin-project/lotus/build"
|
2020-09-28 21:25:58 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/adt"
|
2020-06-22 22:38:36 +00:00
|
|
|
"github.com/filecoin-project/lotus/journal"
|
2020-03-02 00:26:09 +00:00
|
|
|
"github.com/filecoin-project/lotus/metrics"
|
2020-07-23 02:05:11 +00:00
|
|
|
|
2020-03-02 00:26:09 +00:00
|
|
|
"go.opencensus.io/stats"
|
2019-10-13 09:08:34 +00:00
|
|
|
"go.opencensus.io/trace"
|
2019-11-25 16:04:03 +00:00
|
|
|
"go.uber.org/multierr"
|
2019-09-30 23:55:35 +00:00
|
|
|
|
2019-10-18 04:47:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2019-07-08 12:51:45 +00:00
|
|
|
|
2019-10-24 06:24:58 +00:00
|
|
|
lru "github.com/hashicorp/golang-lru"
|
2019-07-26 04:54:22 +00:00
|
|
|
block "github.com/ipfs/go-block-format"
|
2019-07-05 14:29:17 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
2020-10-09 20:41:34 +00:00
|
|
|
"github.com/ipfs/go-datastore"
|
2019-07-05 14:29:17 +00:00
|
|
|
dstore "github.com/ipfs/go-datastore"
|
2020-10-09 20:41:34 +00:00
|
|
|
"github.com/ipfs/go-datastore/query"
|
2020-02-04 22:19:05 +00:00
|
|
|
cbor "github.com/ipfs/go-ipld-cbor"
|
2020-01-08 19:10:57 +00:00
|
|
|
logging "github.com/ipfs/go-log/v2"
|
2020-11-04 14:39:01 +00:00
|
|
|
"github.com/whyrusleeping/pubsub"
|
2019-09-17 01:56:37 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-07-05 14:29:17 +00:00
|
|
|
)
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
var log = logging.Logger("chainstore")
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2021-04-28 19:49:21 +00:00
|
|
|
var (
|
|
|
|
chainHeadKey = dstore.NewKey("head")
|
|
|
|
checkpointKey = dstore.NewKey("/chain/checks")
|
|
|
|
blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation")
|
|
|
|
)
|
2019-07-24 02:45:00 +00:00
|
|
|
|
2020-08-11 22:07:17 +00:00
|
|
|
var DefaultTipSetCacheSize = 8192
|
2020-09-02 18:43:05 +00:00
|
|
|
var DefaultMsgMetaCacheSize = 2048
|
2020-08-11 22:07:17 +00:00
|
|
|
|
2020-09-08 06:16:34 +00:00
|
|
|
var ErrNotifeeDone = errors.New("notifee is done and should be removed")
|
|
|
|
|
2020-08-11 22:07:17 +00:00
|
|
|
func init() {
|
|
|
|
if s := os.Getenv("LOTUS_CHAIN_TIPSET_CACHE"); s != "" {
|
|
|
|
tscs, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to parse 'LOTUS_CHAIN_TIPSET_CACHE' env var: %s", err)
|
|
|
|
}
|
|
|
|
DefaultTipSetCacheSize = tscs
|
|
|
|
}
|
2020-09-02 18:43:05 +00:00
|
|
|
|
|
|
|
if s := os.Getenv("LOTUS_CHAIN_MSGMETA_CACHE"); s != "" {
|
|
|
|
mmcs, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to parse 'LOTUS_CHAIN_MSGMETA_CACHE' env var: %s", err)
|
|
|
|
}
|
|
|
|
DefaultMsgMetaCacheSize = mmcs
|
|
|
|
}
|
2020-08-11 22:07:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-12 18:16:54 +00:00
|
|
|
// ReorgNotifee represents a callback that gets called upon reorgs.
|
2021-03-01 17:38:02 +00:00
|
|
|
type ReorgNotifee = func(rev, app []*types.TipSet) error
|
2020-06-12 18:16:54 +00:00
|
|
|
|
2020-07-17 17:54:26 +00:00
|
|
|
// Journal event types.
|
|
|
|
const (
|
|
|
|
evtTypeHeadChange = iota
|
|
|
|
)
|
|
|
|
|
2020-07-20 13:45:17 +00:00
|
|
|
type HeadChangeEvt struct {
|
|
|
|
From types.TipSetKey
|
|
|
|
FromHeight abi.ChainEpoch
|
|
|
|
To types.TipSetKey
|
|
|
|
ToHeight abi.ChainEpoch
|
|
|
|
RevertCount int
|
|
|
|
ApplyCount int
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// ChainStore is the main point of access to chain data.
|
|
|
|
//
|
|
|
|
// Raw chain data is stored in the Blockstore, with relevant markers (genesis,
|
|
|
|
// latest head tipset references) being tracked in the Datastore (key-value
|
|
|
|
// store).
|
|
|
|
//
|
|
|
|
// To alleviate disk access, the ChainStore has two ARC caches:
|
|
|
|
// 1. a tipset cache
|
|
|
|
// 2. a block => messages references cache.
|
2019-07-05 14:29:17 +00:00
|
|
|
type ChainStore struct {
|
2021-02-28 22:48:36 +00:00
|
|
|
chainBlockstore bstore.Blockstore
|
|
|
|
stateBlockstore bstore.Blockstore
|
|
|
|
metadataDs dstore.Batching
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
chainLocalBlockstore bstore.Blockstore
|
2020-11-10 13:35:36 +00:00
|
|
|
|
2021-04-06 11:01:48 +00:00
|
|
|
heaviestLk sync.RWMutex
|
2019-07-26 04:54:22 +00:00
|
|
|
heaviest *types.TipSet
|
2021-04-28 19:49:21 +00:00
|
|
|
checkpoint *types.TipSet
|
2019-07-05 14:29:17 +00:00
|
|
|
|
|
|
|
bestTips *pubsub.PubSub
|
2019-09-17 22:43:47 +00:00
|
|
|
pubLk sync.Mutex
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-09-03 04:36:07 +00:00
|
|
|
tstLk sync.Mutex
|
2020-02-08 02:18:32 +00:00
|
|
|
tipsets map[abi.ChainEpoch][]cid.Cid
|
2019-09-03 04:36:07 +00:00
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
cindex *ChainIndex
|
|
|
|
|
2020-06-12 18:16:54 +00:00
|
|
|
reorgCh chan<- reorg
|
|
|
|
reorgNotifeeCh chan ReorgNotifee
|
2019-10-24 06:24:58 +00:00
|
|
|
|
2021-07-27 13:34:39 +00:00
|
|
|
mmCache *lru.ARCCache // msg meta cache (mh.Messages -> secp, bls []cid)
|
2019-12-16 19:22:56 +00:00
|
|
|
tsCache *lru.ARCCache
|
2020-01-13 20:47:27 +00:00
|
|
|
|
2020-07-17 17:54:26 +00:00
|
|
|
evtTypes [1]journal.EventType
|
2020-10-09 19:52:04 +00:00
|
|
|
journal journal.Journal
|
2020-11-16 22:22:08 +00:00
|
|
|
|
|
|
|
cancelFn context.CancelFunc
|
|
|
|
wg sync.WaitGroup
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2021-07-27 13:30:23 +00:00
|
|
|
func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, j journal.Journal) *ChainStore {
|
2021-02-28 22:48:36 +00:00
|
|
|
c, _ := lru.NewARC(DefaultMsgMetaCacheSize)
|
|
|
|
tsc, _ := lru.NewARC(DefaultTipSetCacheSize)
|
2020-10-09 19:52:04 +00:00
|
|
|
if j == nil {
|
|
|
|
j = journal.NilJournal()
|
|
|
|
}
|
2020-11-16 22:22:08 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2021-02-28 22:48:36 +00:00
|
|
|
// unwraps the fallback store in case one is configured.
|
|
|
|
// some methods _need_ to operate on a local blockstore only.
|
|
|
|
localbs, _ := bstore.UnwrapFallbackStore(chainBs)
|
2019-07-28 05:35:32 +00:00
|
|
|
cs := &ChainStore{
|
2021-02-28 22:48:36 +00:00
|
|
|
chainBlockstore: chainBs,
|
|
|
|
stateBlockstore: stateBs,
|
|
|
|
chainLocalBlockstore: localbs,
|
|
|
|
metadataDs: ds,
|
|
|
|
bestTips: pubsub.New(64),
|
|
|
|
tipsets: make(map[abi.ChainEpoch][]cid.Cid),
|
|
|
|
mmCache: c,
|
|
|
|
tsCache: tsc,
|
|
|
|
cancelFn: cancel,
|
|
|
|
journal: j,
|
2020-11-10 13:35:36 +00:00
|
|
|
}
|
|
|
|
|
2020-07-17 17:54:26 +00:00
|
|
|
cs.evtTypes = [1]journal.EventType{
|
2020-10-09 19:52:04 +00:00
|
|
|
evtTypeHeadChange: j.RegisterEventType("sync", "head_change"),
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-07-28 05:35:32 +00:00
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
ci := NewChainIndex(cs.LoadTipSet)
|
|
|
|
|
|
|
|
cs.cindex = ci
|
|
|
|
|
2019-07-28 05:35:32 +00:00
|
|
|
hcnf := func(rev, app []*types.TipSet) error {
|
2019-09-17 22:43:47 +00:00
|
|
|
cs.pubLk.Lock()
|
|
|
|
defer cs.pubLk.Unlock()
|
2019-09-18 11:01:52 +00:00
|
|
|
|
2020-04-23 22:15:00 +00:00
|
|
|
notif := make([]*api.HeadChange, len(rev)+len(app))
|
2019-09-18 11:01:52 +00:00
|
|
|
|
|
|
|
for i, r := range rev {
|
2020-04-23 22:15:00 +00:00
|
|
|
notif[i] = &api.HeadChange{
|
2019-07-28 05:35:32 +00:00
|
|
|
Type: HCRevert,
|
|
|
|
Val: r,
|
2019-09-18 11:01:52 +00:00
|
|
|
}
|
2019-07-28 05:35:32 +00:00
|
|
|
}
|
2019-09-18 11:01:52 +00:00
|
|
|
for i, r := range app {
|
2020-04-23 22:15:00 +00:00
|
|
|
notif[i+len(rev)] = &api.HeadChange{
|
2019-07-28 05:35:32 +00:00
|
|
|
Type: HCApply,
|
|
|
|
Val: r,
|
2019-09-18 11:01:52 +00:00
|
|
|
}
|
2019-07-28 05:35:32 +00:00
|
|
|
}
|
2019-09-18 11:01:52 +00:00
|
|
|
|
|
|
|
cs.bestTips.Pub(notif, "headchange")
|
2019-07-28 05:35:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-02 00:26:09 +00:00
|
|
|
hcmetric := func(rev, app []*types.TipSet) error {
|
|
|
|
for _, r := range app {
|
2020-11-16 22:22:08 +00:00
|
|
|
stats.Record(context.Background(), metrics.ChainNodeHeight.M(int64(r.Height())))
|
2020-03-02 00:26:09 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-12 18:16:54 +00:00
|
|
|
cs.reorgNotifeeCh = make(chan ReorgNotifee)
|
2020-11-16 15:52:19 +00:00
|
|
|
cs.reorgCh = cs.reorgWorker(ctx, []ReorgNotifee{hcnf, hcmetric})
|
2019-07-28 05:35:32 +00:00
|
|
|
|
|
|
|
return cs
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:22:08 +00:00
|
|
|
func (cs *ChainStore) Close() error {
|
|
|
|
cs.cancelFn()
|
|
|
|
cs.wg.Wait()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-24 02:45:00 +00:00
|
|
|
func (cs *ChainStore) Load() error {
|
2021-04-28 19:49:21 +00:00
|
|
|
if err := cs.loadHead(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := cs.loadCheckpoint(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (cs *ChainStore) loadHead() error {
|
2021-02-28 22:48:36 +00:00
|
|
|
head, err := cs.metadataDs.Get(chainHeadKey)
|
2019-07-24 21:10:27 +00:00
|
|
|
if err == dstore.ErrNotFound {
|
|
|
|
log.Warn("no previous chain state found")
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-24 02:45:00 +00:00
|
|
|
if err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("failed to load chain state from datastore: %w", err)
|
2019-07-24 02:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var tscids []cid.Cid
|
|
|
|
if err := json.Unmarshal(head, &tscids); err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("failed to unmarshal stored chain head: %w", err)
|
2019-07-24 02:45:00 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 19:22:56 +00:00
|
|
|
ts, err := cs.LoadTipSet(types.NewTipSetKey(tscids...))
|
2019-07-24 02:45:00 +00:00
|
|
|
if err != nil {
|
2019-09-30 23:55:35 +00:00
|
|
|
return xerrors.Errorf("loading tipset: %w", err)
|
2019-07-24 02:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cs.heaviest = ts
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-28 19:49:21 +00:00
|
|
|
func (cs *ChainStore) loadCheckpoint() error {
|
|
|
|
tskBytes, err := cs.metadataDs.Get(checkpointKey)
|
|
|
|
if err == dstore.ErrNotFound {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to load checkpoint from datastore: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var tsk types.TipSetKey
|
|
|
|
err = json.Unmarshal(tskBytes, &tsk)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ts, err := cs.LoadTipSet(tsk)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("loading tipset: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cs.checkpoint = ts
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
func (cs *ChainStore) writeHead(ts *types.TipSet) error {
|
2019-07-24 02:45:00 +00:00
|
|
|
data, err := json.Marshal(ts.Cids())
|
|
|
|
if err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("failed to marshal tipset: %w", err)
|
2019-07-24 02:45:00 +00:00
|
|
|
}
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
if err := cs.metadataDs.Put(chainHeadKey, data); err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("failed to write chain head to datastore: %w", err)
|
2019-07-24 02:45:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-17 03:05:55 +00:00
|
|
|
const (
|
2019-09-17 22:43:47 +00:00
|
|
|
HCRevert = "revert"
|
|
|
|
HCApply = "apply"
|
|
|
|
HCCurrent = "current"
|
2019-07-17 03:05:55 +00:00
|
|
|
)
|
|
|
|
|
2020-04-23 22:15:00 +00:00
|
|
|
func (cs *ChainStore) SubHeadChanges(ctx context.Context) chan []*api.HeadChange {
|
2019-09-17 22:43:47 +00:00
|
|
|
cs.pubLk.Lock()
|
2019-07-17 03:05:55 +00:00
|
|
|
subch := cs.bestTips.Sub("headchange")
|
2019-09-17 22:43:47 +00:00
|
|
|
head := cs.GetHeaviestTipSet()
|
|
|
|
cs.pubLk.Unlock()
|
|
|
|
|
2020-04-23 22:15:00 +00:00
|
|
|
out := make(chan []*api.HeadChange, 16)
|
|
|
|
out <- []*api.HeadChange{{
|
2019-09-17 22:43:47 +00:00
|
|
|
Type: HCCurrent,
|
|
|
|
Val: head,
|
2019-09-18 11:01:52 +00:00
|
|
|
}}
|
2019-09-17 22:43:47 +00:00
|
|
|
|
2019-07-17 03:05:55 +00:00
|
|
|
go func() {
|
|
|
|
defer close(out)
|
2019-12-11 12:37:52 +00:00
|
|
|
var unsubOnce sync.Once
|
|
|
|
|
2019-07-28 19:19:33 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case val, ok := <-subch:
|
|
|
|
if !ok {
|
2019-08-31 01:03:10 +00:00
|
|
|
log.Warn("chain head sub exit loop")
|
2019-07-28 19:19:33 +00:00
|
|
|
return
|
|
|
|
}
|
2020-11-03 12:28:31 +00:00
|
|
|
if len(out) > 5 {
|
2019-09-18 11:01:52 +00:00
|
|
|
log.Warnf("head change sub is slow, has %d buffered entries", len(out))
|
|
|
|
}
|
2019-08-31 01:03:10 +00:00
|
|
|
select {
|
2020-04-23 22:15:00 +00:00
|
|
|
case out <- val.([]*api.HeadChange):
|
2019-08-31 01:03:10 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
}
|
2019-07-28 19:19:33 +00:00
|
|
|
case <-ctx.Done():
|
2019-12-11 12:37:52 +00:00
|
|
|
unsubOnce.Do(func() {
|
|
|
|
go cs.bestTips.Unsub(subch)
|
|
|
|
})
|
2019-07-28 19:19:33 +00:00
|
|
|
}
|
2019-07-17 03:05:55 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2020-06-12 18:16:54 +00:00
|
|
|
func (cs *ChainStore) SubscribeHeadChanges(f ReorgNotifee) {
|
|
|
|
cs.reorgNotifeeCh <- f
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 09:49:20 +00:00
|
|
|
func (cs *ChainStore) IsBlockValidated(ctx context.Context, blkid cid.Cid) (bool, error) {
|
|
|
|
key := blockValidationCacheKeyPrefix.Instance(blkid.String())
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
return cs.metadataDs.Has(key)
|
2020-06-14 09:49:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ChainStore) MarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error {
|
|
|
|
key := blockValidationCacheKeyPrefix.Instance(blkid.String())
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
if err := cs.metadataDs.Put(key, []byte{0}); err != nil {
|
2020-06-14 09:49:20 +00:00
|
|
|
return xerrors.Errorf("cache block validation: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-27 21:52:26 +00:00
|
|
|
func (cs *ChainStore) UnmarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error {
|
|
|
|
key := blockValidationCacheKeyPrefix.Instance(blkid.String())
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
if err := cs.metadataDs.Delete(key); err != nil {
|
2020-09-27 21:52:26 +00:00
|
|
|
return xerrors.Errorf("removing from valid block cache: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
func (cs *ChainStore) SetGenesis(b *types.BlockHeader) error {
|
2019-10-06 03:32:56 +00:00
|
|
|
ts, err := types.NewTipSet([]*types.BlockHeader{b})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-15 04:33:29 +00:00
|
|
|
if err := cs.PutTipSet(context.TODO(), ts); err != nil {
|
2019-07-05 14:29:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
return cs.metadataDs.Put(dstore.NewKey("0"), b.Cid().Bytes())
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-15 04:33:29 +00:00
|
|
|
func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error {
|
2019-10-06 03:32:56 +00:00
|
|
|
for _, b := range ts.Blocks() {
|
2019-11-12 10:18:46 +00:00
|
|
|
if err := cs.PersistBlockHeaders(b); err != nil {
|
2019-07-05 14:29:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
expanded, err := cs.expandTipset(ts.Blocks()[0])
|
2019-09-08 20:14:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("errored while expanding tipset: %w", err)
|
|
|
|
}
|
2019-10-06 03:32:56 +00:00
|
|
|
log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids())
|
2019-09-08 20:14:01 +00:00
|
|
|
|
2019-10-15 04:33:29 +00:00
|
|
|
if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err)
|
2019-07-28 05:35:32 +00:00
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// MaybeTakeHeavierTipSet evaluates the incoming tipset and locks it in our
|
|
|
|
// internal state as our new head, if and only if it is heavier than the current
|
2021-01-18 15:34:25 +00:00
|
|
|
// head and does not exceed the maximum fork length.
|
2019-10-15 04:33:29 +00:00
|
|
|
func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error {
|
2021-06-18 17:15:08 +00:00
|
|
|
for {
|
|
|
|
cs.heaviestLk.Lock()
|
|
|
|
if len(cs.reorgCh) < reorgChBuf/2 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
cs.heaviestLk.Unlock()
|
|
|
|
log.Errorf("reorg channel is heavily backlogged, waiting a bit before trying to take process new tipsets")
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Second / 2):
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
defer cs.heaviestLk.Unlock()
|
2019-10-15 04:33:29 +00:00
|
|
|
w, err := cs.Weight(ctx, ts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
heaviestW, err := cs.Weight(ctx, cs.heaviest)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.GreaterThan(heaviestW) {
|
2019-07-31 07:13:49 +00:00
|
|
|
// 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.
|
2021-01-18 15:34:25 +00:00
|
|
|
|
|
|
|
exceeds, err := cs.exceedsForkLength(cs.heaviest, ts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if exceeds {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-09 20:14:40 +00:00
|
|
|
return cs.takeHeaviestTipSet(ctx, ts)
|
2020-10-28 15:53:13 +00:00
|
|
|
} else if w.Equals(heaviestW) && !ts.Equals(cs.heaviest) {
|
|
|
|
log.Errorw("weight draw", "currTs", cs.heaviest, "ts", ts)
|
2019-10-10 03:50:50 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-18 15:34:25 +00:00
|
|
|
// Check if the two tipsets have a fork length above `ForkLengthThreshold`.
|
|
|
|
// `synced` is the head of the chain we are currently synced to and `external`
|
|
|
|
// is the incoming tipset potentially belonging to a forked chain. It assumes
|
|
|
|
// the external chain has already been validated and available in the ChainStore.
|
|
|
|
// The "fast forward" case is covered in this logic as a valid fork of length 0.
|
|
|
|
//
|
|
|
|
// FIXME: We may want to replace some of the logic in `syncFork()` with this.
|
|
|
|
// `syncFork()` counts the length on both sides of the fork at the moment (we
|
|
|
|
// need to settle on that) but here we just enforce it on the `synced` side.
|
|
|
|
func (cs *ChainStore) exceedsForkLength(synced, external *types.TipSet) (bool, error) {
|
|
|
|
if synced == nil || external == nil {
|
|
|
|
// FIXME: If `cs.heaviest` is nil we should just bypass the entire
|
|
|
|
// `MaybeTakeHeavierTipSet` logic (instead of each of the called
|
|
|
|
// functions having to handle the nil case on their own).
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
// `forkLength`: number of tipsets we need to walk back from the our `synced`
|
|
|
|
// chain to the common ancestor with the new `external` head in order to
|
|
|
|
// adopt the fork.
|
|
|
|
for forkLength := 0; forkLength < int(build.ForkLengthThreshold); forkLength++ {
|
|
|
|
// First walk back as many tipsets in the external chain to match the
|
|
|
|
// `synced` height to compare them. If we go past the `synced` height
|
|
|
|
// the subsequent match will fail but it will still be useful to get
|
|
|
|
// closer to the `synced` head parent's height in the next loop.
|
|
|
|
for external.Height() > synced.Height() {
|
|
|
|
if external.Height() == 0 {
|
|
|
|
// We reached the genesis of the external chain without a match;
|
|
|
|
// this is considered a fork outside the allowed limit (of "infinite"
|
|
|
|
// length).
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
external, err = cs.LoadTipSet(external.Parents())
|
|
|
|
if err != nil {
|
|
|
|
return false, xerrors.Errorf("failed to load parent tipset in external chain: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now check if we arrived at the common ancestor.
|
|
|
|
if synced.Equals(external) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2021-04-28 19:49:21 +00:00
|
|
|
// Now check to see if we've walked back to the checkpoint.
|
|
|
|
if synced.Equals(cs.checkpoint) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2021-01-18 15:34:25 +00:00
|
|
|
// If we didn't, go back *one* tipset on the `synced` side (incrementing
|
|
|
|
// the `forkLength`).
|
|
|
|
if synced.Height() == 0 {
|
|
|
|
// Same check as the `external` side, if we reach the start (genesis)
|
|
|
|
// there is no common ancestor.
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
synced, err = cs.LoadTipSet(synced.Parents())
|
|
|
|
if err != nil {
|
|
|
|
return false, xerrors.Errorf("failed to load parent tipset in synced chain: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We traversed the fork length allowed without finding a common ancestor.
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-11-04 14:39:01 +00:00
|
|
|
// ForceHeadSilent forces a chain head tipset without triggering a reorg
|
|
|
|
// operation.
|
|
|
|
//
|
|
|
|
// CAUTION: Use it only for testing, such as to teleport the chain to a
|
|
|
|
// particular tipset to carry out a benchmark, verification, etc. on a chain
|
|
|
|
// segment.
|
|
|
|
func (cs *ChainStore) ForceHeadSilent(_ context.Context, ts *types.TipSet) error {
|
2020-11-16 16:03:47 +00:00
|
|
|
log.Warnf("(!!!) forcing a new head silently; new head: %s", ts)
|
2020-11-04 14:39:01 +00:00
|
|
|
|
|
|
|
cs.heaviestLk.Lock()
|
|
|
|
defer cs.heaviestLk.Unlock()
|
2021-04-28 19:49:21 +00:00
|
|
|
if err := cs.removeCheckpoint(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-04 14:39:01 +00:00
|
|
|
cs.heaviest = ts
|
|
|
|
|
|
|
|
err := cs.writeHead(ts)
|
|
|
|
if err != nil {
|
|
|
|
err = xerrors.Errorf("failed to write chain head: %s", err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-10 23:50:49 +00:00
|
|
|
type reorg struct {
|
|
|
|
old *types.TipSet
|
|
|
|
new *types.TipSet
|
|
|
|
}
|
|
|
|
|
2021-06-18 17:15:08 +00:00
|
|
|
const reorgChBuf = 32
|
|
|
|
|
2020-06-12 18:16:54 +00:00
|
|
|
func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNotifee) chan<- reorg {
|
2021-06-18 17:15:08 +00:00
|
|
|
out := make(chan reorg, reorgChBuf)
|
2020-06-12 18:16:54 +00:00
|
|
|
notifees := make([]ReorgNotifee, len(initialNotifees))
|
|
|
|
copy(notifees, initialNotifees)
|
|
|
|
|
2020-11-16 22:22:08 +00:00
|
|
|
cs.wg.Add(1)
|
2019-10-10 23:50:49 +00:00
|
|
|
go func() {
|
2020-11-16 22:22:08 +00:00
|
|
|
defer cs.wg.Done()
|
2019-10-10 23:50:49 +00:00
|
|
|
defer log.Warn("reorgWorker quit")
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
2020-06-12 18:16:54 +00:00
|
|
|
case n := <-cs.reorgNotifeeCh:
|
|
|
|
notifees = append(notifees, n)
|
|
|
|
|
2019-10-10 23:50:49 +00:00
|
|
|
case r := <-out:
|
|
|
|
revert, apply, err := cs.ReorgOps(r.old, r.new)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("computing reorg ops failed: ", err)
|
|
|
|
continue
|
|
|
|
}
|
2019-11-18 18:18:59 +00:00
|
|
|
|
2020-10-09 19:52:04 +00:00
|
|
|
cs.journal.RecordEvent(cs.evtTypes[evtTypeHeadChange], func() interface{} {
|
2020-07-20 13:45:17 +00:00
|
|
|
return HeadChangeEvt{
|
2020-07-17 17:54:26 +00:00
|
|
|
From: r.old.Key(),
|
|
|
|
FromHeight: r.old.Height(),
|
|
|
|
To: r.new.Key(),
|
|
|
|
ToHeight: r.new.Height(),
|
|
|
|
RevertCount: len(revert),
|
|
|
|
ApplyCount: len(apply),
|
|
|
|
}
|
2020-06-22 22:38:36 +00:00
|
|
|
})
|
|
|
|
|
2019-11-18 18:18:59 +00:00
|
|
|
// reverse the apply array
|
|
|
|
for i := len(apply)/2 - 1; i >= 0; i-- {
|
|
|
|
opp := len(apply) - 1 - i
|
|
|
|
apply[i], apply[opp] = apply[opp], apply[i]
|
|
|
|
}
|
|
|
|
|
2020-09-08 06:16:34 +00:00
|
|
|
var toremove map[int]struct{}
|
|
|
|
for i, hcf := range notifees {
|
|
|
|
err := hcf(revert, apply)
|
|
|
|
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
|
|
|
|
case ErrNotifeeDone:
|
|
|
|
if toremove == nil {
|
|
|
|
toremove = make(map[int]struct{})
|
|
|
|
}
|
|
|
|
toremove[i] = struct{}{}
|
|
|
|
|
|
|
|
default:
|
2019-10-10 23:50:49 +00:00
|
|
|
log.Error("head change func errored (BAD): ", err)
|
|
|
|
}
|
|
|
|
}
|
2020-09-08 06:16:34 +00:00
|
|
|
|
|
|
|
if len(toremove) > 0 {
|
|
|
|
newNotifees := make([]ReorgNotifee, 0, len(notifees)-len(toremove))
|
|
|
|
for i, hcf := range notifees {
|
|
|
|
_, remove := toremove[i]
|
|
|
|
if remove {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
newNotifees = append(newNotifees, hcf)
|
|
|
|
}
|
|
|
|
notifees = newNotifees
|
|
|
|
}
|
|
|
|
|
2019-10-10 23:50:49 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// takeHeaviestTipSet actually sets the incoming tipset as our head both in
|
|
|
|
// memory and in the ChainStore. It also sends a notification to deliver to
|
|
|
|
// ReorgNotifees.
|
2019-11-09 20:14:40 +00:00
|
|
|
func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error {
|
2019-12-05 05:14:19 +00:00
|
|
|
_, span := trace.StartSpan(ctx, "takeHeaviestTipSet")
|
2019-11-09 20:14:40 +00:00
|
|
|
defer span.End()
|
|
|
|
|
2019-10-10 23:50:49 +00:00
|
|
|
if cs.heaviest != nil { // buf
|
|
|
|
if len(cs.reorgCh) > 0 {
|
|
|
|
log.Warnf("Reorg channel running behind, %d reorgs buffered", len(cs.reorgCh))
|
2019-10-10 03:50:50 +00:00
|
|
|
}
|
2019-10-10 23:50:49 +00:00
|
|
|
cs.reorgCh <- reorg{
|
|
|
|
old: cs.heaviest,
|
|
|
|
new: ts,
|
2019-07-17 03:05:55 +00:00
|
|
|
}
|
2019-10-10 03:50:50 +00:00
|
|
|
} else {
|
2019-10-16 08:01:41 +00:00
|
|
|
log.Warnf("no heaviest tipset found, using %s", ts.Cids())
|
2019-10-10 03:50:50 +00:00
|
|
|
}
|
2019-08-14 03:31:58 +00:00
|
|
|
|
2019-11-09 20:14:40 +00:00
|
|
|
span.AddAttributes(trace.BoolAttribute("newHead", true))
|
|
|
|
|
2019-12-03 23:04:52 +00:00
|
|
|
log.Infof("New heaviest tipset! %s (height=%d)", ts.Cids(), ts.Height())
|
2019-10-10 03:50:50 +00:00
|
|
|
cs.heaviest = ts
|
2019-07-24 02:45:00 +00:00
|
|
|
|
2019-10-10 03:50:50 +00:00
|
|
|
if err := cs.writeHead(ts); err != nil {
|
|
|
|
log.Errorf("failed to write chain head: %s", err)
|
|
|
|
return nil
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-10-10 03:50:50 +00:00
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-09 20:41:34 +00:00
|
|
|
// 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 {
|
2021-02-28 22:48:36 +00:00
|
|
|
return FlushValidationCache(cs.metadataDs)
|
2021-01-15 19:57:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func FlushValidationCache(ds datastore.Batching) error {
|
2020-10-09 20:41:34 +00:00
|
|
|
log.Infof("clearing block validation cache...")
|
|
|
|
|
2021-01-15 19:57:32 +00:00
|
|
|
dsWalk, err := ds.Query(query.Query{
|
2020-10-10 13:36:32 +00:00
|
|
|
// 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,
|
|
|
|
})
|
2020-10-09 20:41:34 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-01-15 19:57:32 +00:00
|
|
|
batch, err := ds.Batch()
|
2020-10-09 20:41:34 +00:00
|
|
|
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++
|
2020-10-10 02:00:12 +00:00
|
|
|
batch.Delete(datastore.RawKey(k.Key)) // nolint:errcheck
|
2020-10-09 20:41:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-10 03:50:50 +00:00
|
|
|
// SetHead sets the chainstores current 'best' head node.
|
2021-04-28 19:49:21 +00:00
|
|
|
// This should only be called if something is broken and needs fixing.
|
|
|
|
//
|
|
|
|
// This function will bypass and remove any checkpoints.
|
2019-10-10 03:50:50 +00:00
|
|
|
func (cs *ChainStore) SetHead(ts *types.TipSet) error {
|
|
|
|
cs.heaviestLk.Lock()
|
|
|
|
defer cs.heaviestLk.Unlock()
|
2021-04-28 19:49:21 +00:00
|
|
|
if err := cs.removeCheckpoint(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-09 20:14:40 +00:00
|
|
|
return cs.takeHeaviestTipSet(context.TODO(), ts)
|
2019-10-10 03:50:50 +00:00
|
|
|
}
|
|
|
|
|
2021-04-28 19:49:21 +00:00
|
|
|
// RemoveCheckpoint removes the current checkpoint.
|
|
|
|
func (cs *ChainStore) RemoveCheckpoint() error {
|
|
|
|
cs.heaviestLk.Lock()
|
|
|
|
defer cs.heaviestLk.Unlock()
|
|
|
|
return cs.removeCheckpoint()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ChainStore) removeCheckpoint() error {
|
|
|
|
if err := cs.metadataDs.Delete(checkpointKey); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cs.checkpoint = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetCheckpoint will set a checkpoint past which the chainstore will not allow forks.
|
|
|
|
//
|
|
|
|
// NOTE: Checkpoints cannot be set beyond ForkLengthThreshold epochs in the past.
|
|
|
|
func (cs *ChainStore) SetCheckpoint(ts *types.TipSet) error {
|
|
|
|
tskBytes, err := json.Marshal(ts.Key())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cs.heaviestLk.Lock()
|
|
|
|
defer cs.heaviestLk.Unlock()
|
|
|
|
|
|
|
|
if ts.Height() > cs.heaviest.Height() {
|
|
|
|
return xerrors.Errorf("cannot set a checkpoint in the future")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, this operation could get _very_ expensive.
|
|
|
|
if cs.heaviest.Height()-ts.Height() > build.ForkLengthThreshold {
|
|
|
|
return xerrors.Errorf("cannot set a checkpoint before the fork threshold")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ts.Equals(cs.heaviest) {
|
|
|
|
anc, err := cs.IsAncestorOf(ts, cs.heaviest)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("cannot determine whether checkpoint tipset is in main-chain: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !anc {
|
|
|
|
return xerrors.Errorf("cannot mark tipset as checkpoint, since it isn't in the main-chain: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = cs.metadataDs.Put(checkpointKey, tskBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cs.checkpoint = ts
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ChainStore) GetCheckpoint() *types.TipSet {
|
|
|
|
cs.heaviestLk.RLock()
|
|
|
|
chkpt := cs.checkpoint
|
|
|
|
cs.heaviestLk.RUnlock()
|
|
|
|
return chkpt
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// Contains returns whether our BlockStore has all blocks in the supplied TipSet.
|
2019-07-26 04:54:22 +00:00
|
|
|
func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) {
|
|
|
|
for _, c := range ts.Cids() {
|
2021-02-28 22:48:36 +00:00
|
|
|
has, err := cs.chainBlockstore.Has(c)
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !has {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// GetBlock fetches a BlockHeader with the supplied CID. It returns
|
|
|
|
// blockstore.ErrNotFound if the block was not found in the BlockStore.
|
2019-07-25 22:15:03 +00:00
|
|
|
func (cs *ChainStore) GetBlock(c cid.Cid) (*types.BlockHeader, error) {
|
2020-11-10 13:35:36 +00:00
|
|
|
var blk *types.BlockHeader
|
2021-02-28 22:48:36 +00:00
|
|
|
err := cs.chainLocalBlockstore.View(c, func(b []byte) (err error) {
|
2020-11-10 13:35:36 +00:00
|
|
|
blk, err = types.DecodeBlock(b)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
return blk, err
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 19:22:56 +00:00
|
|
|
func (cs *ChainStore) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) {
|
|
|
|
v, ok := cs.tsCache.Get(tsk)
|
|
|
|
if ok {
|
|
|
|
return v.(*types.TipSet), nil
|
|
|
|
}
|
|
|
|
|
2020-09-28 13:56:44 +00:00
|
|
|
// Fetch tipset block headers from blockstore in parallel
|
|
|
|
var eg errgroup.Group
|
|
|
|
cids := tsk.Cids()
|
2020-09-28 14:35:37 +00:00
|
|
|
blks := make([]*types.BlockHeader, len(cids))
|
|
|
|
for i, c := range cids {
|
|
|
|
i, c := i, c
|
2020-09-28 13:56:44 +00:00
|
|
|
eg.Go(func() error {
|
|
|
|
b, err := cs.GetBlock(c)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("get block %s: %w", c, err)
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2020-09-28 14:35:37 +00:00
|
|
|
blks[i] = b
|
2020-09-28 13:56:44 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
err := eg.Wait()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 19:22:56 +00:00
|
|
|
ts, err := types.NewTipSet(blks)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cs.tsCache.Add(tsk, ts)
|
|
|
|
|
|
|
|
return ts, nil
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-02 14:29:39 +00:00
|
|
|
// IsAncestorOf returns true if 'a' is an ancestor of 'b'
|
2019-07-26 04:54:22 +00:00
|
|
|
func (cs *ChainStore) IsAncestorOf(a, b *types.TipSet) (bool, error) {
|
2019-07-05 14:29:17 +00:00
|
|
|
if b.Height() <= a.Height() {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cur := b
|
|
|
|
for !a.Equals(cur) && cur.Height() > a.Height() {
|
2020-09-09 23:09:55 +00:00
|
|
|
next, err := cs.LoadTipSet(cur.Parents())
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cur = next
|
|
|
|
}
|
|
|
|
|
|
|
|
return cur.Equals(a), nil
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
func (cs *ChainStore) NearestCommonAncestor(a, b *types.TipSet) (*types.TipSet, error) {
|
2019-07-05 14:29:17 +00:00
|
|
|
l, _, err := cs.ReorgOps(a, b)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return cs.LoadTipSet(l[len(l)-1].Parents())
|
|
|
|
}
|
|
|
|
|
2021-05-13 12:08:52 +00:00
|
|
|
// ReorgOps takes two tipsets (which can be at different heights), and walks
|
|
|
|
// their corresponding chains backwards one step at a time until we find
|
|
|
|
// a common ancestor. It then returns the respective chain segments that fork
|
|
|
|
// from the identified ancestor, in reverse order, where the first element of
|
|
|
|
// each slice is the supplied tipset, and the last element is the common
|
2021-05-19 14:08:14 +00:00
|
|
|
// ancestor.
|
2021-05-13 12:08:52 +00:00
|
|
|
//
|
|
|
|
// If an error happens along the way, we return the error with nil slices.
|
2019-07-26 04:54:22 +00:00
|
|
|
func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) {
|
2020-08-25 00:21:03 +00:00
|
|
|
return ReorgOps(cs.LoadTipSet, a, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ReorgOps(lts func(types.TipSetKey) (*types.TipSet, error), a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) {
|
2019-07-05 14:29:17 +00:00
|
|
|
left := a
|
|
|
|
right := b
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
var leftChain, rightChain []*types.TipSet
|
2019-07-05 14:29:17 +00:00
|
|
|
for !left.Equals(right) {
|
|
|
|
if left.Height() > right.Height() {
|
|
|
|
leftChain = append(leftChain, left)
|
2020-08-25 00:21:03 +00:00
|
|
|
par, err := lts(left.Parents())
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
left = par
|
|
|
|
} else {
|
|
|
|
rightChain = append(rightChain, right)
|
2020-08-25 00:21:03 +00:00
|
|
|
par, err := lts(right.Parents())
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
2019-07-31 07:13:49 +00:00
|
|
|
log.Infof("failed to fetch right.Parents: %s", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
right = par
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return leftChain, rightChain, nil
|
2020-08-25 00:21:03 +00:00
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 21:51:25 +00:00
|
|
|
// GetHeaviestTipSet returns the current heaviest tipset known (i.e. our head).
|
2021-04-06 11:01:48 +00:00
|
|
|
func (cs *ChainStore) GetHeaviestTipSet() (ts *types.TipSet) {
|
|
|
|
cs.heaviestLk.RLock()
|
|
|
|
ts = cs.heaviest
|
|
|
|
cs.heaviestLk.RUnlock()
|
|
|
|
return
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-10 03:04:10 +00:00
|
|
|
func (cs *ChainStore) AddToTipSetTracker(b *types.BlockHeader) error {
|
2019-09-03 04:36:07 +00:00
|
|
|
cs.tstLk.Lock()
|
|
|
|
defer cs.tstLk.Unlock()
|
|
|
|
|
|
|
|
tss := cs.tipsets[b.Height]
|
|
|
|
for _, oc := range tss {
|
|
|
|
if oc == b.Cid() {
|
2019-09-25 13:38:59 +00:00
|
|
|
log.Debug("tried to add block to tipset tracker that was already there")
|
2019-09-03 04:36:07 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-11-09 02:57:36 +00:00
|
|
|
h, err := cs.GetBlock(oc)
|
|
|
|
if err == nil && h != nil {
|
|
|
|
if h.Miner == b.Miner {
|
|
|
|
log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache %s-%s", b.Miner, b.Height, b.Cid(), h.Cid())
|
|
|
|
}
|
|
|
|
}
|
2019-09-03 04:36:07 +00:00
|
|
|
}
|
2020-11-26 22:36:38 +00:00
|
|
|
// This function is called 5 times per epoch on average
|
|
|
|
// It is also called with tipsets that are done with initial validation
|
|
|
|
// so they cannot be from the future.
|
|
|
|
// We are guaranteed not to use tipsets older than 900 epochs (fork limit)
|
|
|
|
// This means that we ideally want to keep only most recent 900 epochs in here
|
|
|
|
// Golang's map iteration starts at a random point in a map.
|
|
|
|
// With 5 tries per epoch, and 900 entries to keep, on average we will have
|
|
|
|
// ~136 garbage entires in the `cs.tipsets` map. (solve for 1-(1-x/(900+x))^5 == 0.5)
|
|
|
|
// Seems good enough to me
|
|
|
|
|
|
|
|
for height := range cs.tipsets {
|
|
|
|
if height < b.Height-build.Finality {
|
|
|
|
delete(cs.tipsets, height)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2019-09-03 04:36:07 +00:00
|
|
|
|
|
|
|
cs.tipsets[b.Height] = append(tss, b.Cid())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-25 16:04:03 +00:00
|
|
|
func (cs *ChainStore) PersistBlockHeaders(b ...*types.BlockHeader) error {
|
2019-11-12 10:18:46 +00:00
|
|
|
sbs := make([]block.Block, len(b))
|
|
|
|
|
|
|
|
for i, header := range b {
|
2019-11-25 16:04:03 +00:00
|
|
|
var err error
|
2019-11-12 10:18:46 +00:00
|
|
|
sbs[i], err = header.ToStorageBlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-11-25 16:04:03 +00:00
|
|
|
batchSize := 256
|
|
|
|
calls := len(b) / batchSize
|
|
|
|
|
|
|
|
var err error
|
|
|
|
for i := 0; i <= calls; i++ {
|
|
|
|
start := batchSize * i
|
|
|
|
end := start + batchSize
|
|
|
|
if end > len(b) {
|
|
|
|
end = len(b)
|
|
|
|
}
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
err = multierr.Append(err, cs.chainLocalBlockstore.PutMany(sbs[start:end]))
|
2019-11-25 16:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-09-03 04:36:07 +00:00
|
|
|
func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) {
|
|
|
|
// Hold lock for the whole function for now, if it becomes a problem we can
|
|
|
|
// fix pretty easily
|
|
|
|
cs.tstLk.Lock()
|
|
|
|
defer cs.tstLk.Unlock()
|
|
|
|
|
|
|
|
all := []*types.BlockHeader{b}
|
|
|
|
|
|
|
|
tsets, ok := cs.tipsets[b.Height]
|
|
|
|
if !ok {
|
|
|
|
return types.NewTipSet(all)
|
|
|
|
}
|
|
|
|
|
2020-11-09 02:57:36 +00:00
|
|
|
inclMiners := map[address.Address]cid.Cid{b.Miner: b.Cid()}
|
2019-09-03 04:36:07 +00:00
|
|
|
for _, bhc := range tsets {
|
|
|
|
if bhc == b.Cid() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
h, err := cs.GetBlock(bhc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to load block (%s) for tipset expansion: %w", bhc, err)
|
|
|
|
}
|
|
|
|
|
2020-11-09 02:57:36 +00:00
|
|
|
if cid, found := inclMiners[h.Miner]; found {
|
|
|
|
log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache %s-%s", h.Miner, h.Height, h.Cid(), cid)
|
2019-12-10 11:07:07 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-09-03 04:36:07 +00:00
|
|
|
if types.CidArrsEqual(h.Parents, b.Parents) {
|
|
|
|
all = append(all, h)
|
2020-11-09 02:57:36 +00:00
|
|
|
inclMiners[h.Miner] = bhc
|
2019-09-03 04:36:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: other validation...?
|
|
|
|
|
|
|
|
return types.NewTipSet(all)
|
|
|
|
}
|
|
|
|
|
2019-10-15 04:33:29 +00:00
|
|
|
func (cs *ChainStore) AddBlock(ctx context.Context, b *types.BlockHeader) error {
|
2019-11-12 10:18:46 +00:00
|
|
|
if err := cs.PersistBlockHeaders(b); err != nil {
|
2019-07-05 14:29:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-09-03 04:36:07 +00:00
|
|
|
ts, err := cs.expandTipset(b)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-15 04:33:29 +00:00
|
|
|
if err := cs.MaybeTakeHeavierTipSet(ctx, ts); err != nil {
|
2019-11-22 16:20:56 +00:00
|
|
|
return xerrors.Errorf("MaybeTakeHeavierTipSet failed: %w", err)
|
2019-07-28 05:35:32 +00:00
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
func (cs *ChainStore) GetGenesis() (*types.BlockHeader, error) {
|
2021-02-28 22:48:36 +00:00
|
|
|
data, err := cs.metadataDs.Get(dstore.NewKey("0"))
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := cid.Cast(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-10 15:36:03 +00:00
|
|
|
return cs.GetBlock(c)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2021-05-14 20:00:13 +00:00
|
|
|
// GetPath returns the sequence of atomic head change operations that
|
2021-05-13 12:08:52 +00:00
|
|
|
// need to be applied in order to switch the head of the chain from the `from`
|
|
|
|
// tipset to the `to` tipset.
|
2020-04-23 22:15:00 +00:00
|
|
|
func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*api.HeadChange, error) {
|
2020-01-15 00:24:08 +00:00
|
|
|
fts, err := cs.LoadTipSet(from)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("loading from tipset %s: %w", from, err)
|
|
|
|
}
|
|
|
|
tts, err := cs.LoadTipSet(to)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("loading to tipset %s: %w", to, err)
|
|
|
|
}
|
|
|
|
revert, apply, err := cs.ReorgOps(fts, tts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("error getting tipset branches: %w", err)
|
|
|
|
}
|
|
|
|
|
2020-04-23 22:15:00 +00:00
|
|
|
path := make([]*api.HeadChange, len(revert)+len(apply))
|
2020-01-15 00:24:08 +00:00
|
|
|
for i, r := range revert {
|
2020-04-23 22:15:00 +00:00
|
|
|
path[i] = &api.HeadChange{Type: HCRevert, Val: r}
|
2020-01-15 00:24:08 +00:00
|
|
|
}
|
|
|
|
for j, i := 0, len(apply)-1; i >= 0; j, i = j+1, i-1 {
|
2020-04-23 22:15:00 +00:00
|
|
|
path[j+len(revert)] = &api.HeadChange{Type: HCApply, Val: apply[i]}
|
2020-01-15 00:24:08 +00:00
|
|
|
}
|
|
|
|
return path, nil
|
|
|
|
}
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
// ChainBlockstore returns the chain blockstore. Currently the chain and state
|
|
|
|
// // stores are both backed by the same physical store, albeit with different
|
|
|
|
// // caching policies, but in the future they will segregate.
|
|
|
|
func (cs *ChainStore) ChainBlockstore() bstore.Blockstore {
|
|
|
|
return cs.chainBlockstore
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateBlockstore returns the state blockstore. Currently the chain and state
|
|
|
|
// stores are both backed by the same physical store, albeit with different
|
|
|
|
// caching policies, but in the future they will segregate.
|
|
|
|
func (cs *ChainStore) StateBlockstore() bstore.Blockstore {
|
|
|
|
return cs.stateBlockstore
|
2019-07-26 04:54:22 +00:00
|
|
|
}
|
2019-08-02 00:13:57 +00:00
|
|
|
|
2020-07-23 02:05:11 +00:00
|
|
|
func ActorStore(ctx context.Context, bs bstore.Blockstore) adt.Store {
|
2020-07-23 00:14:54 +00:00
|
|
|
return adt.WrapStore(ctx, cbor.NewCborStore(bs))
|
2020-02-08 02:18:32 +00:00
|
|
|
}
|
|
|
|
|
2021-02-28 22:48:36 +00:00
|
|
|
func (cs *ChainStore) ActorStore(ctx context.Context) adt.Store {
|
|
|
|
return ActorStore(ctx, cs.stateBlockstore)
|
2020-02-08 02:18:32 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 00:13:57 +00:00
|
|
|
func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) {
|
|
|
|
var out []*types.FullBlock
|
|
|
|
|
|
|
|
for _, b := range ts.Blocks() {
|
2019-08-01 20:40:47 +00:00
|
|
|
bmsgs, smsgs, err := cs.MessagesForBlock(b)
|
2019-08-02 00:13:57 +00:00
|
|
|
if err != nil {
|
2019-08-02 00:57:29 +00:00
|
|
|
// TODO: check for 'not found' errors, and only return nil if this
|
|
|
|
// is actually a 'not found' error
|
2019-08-02 00:13:57 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fb := &types.FullBlock{
|
2019-08-01 20:40:47 +00:00
|
|
|
Header: b,
|
|
|
|
BlsMessages: bmsgs,
|
|
|
|
SecpkMessages: smsgs,
|
2019-08-02 00:13:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, fb)
|
|
|
|
}
|
|
|
|
return NewFullTipSet(out), nil
|
|
|
|
}
|
2019-09-06 06:26:02 +00:00
|
|
|
|
2020-05-05 17:06:05 +00:00
|
|
|
// GetTipsetByHeight returns the tipset on the chain behind 'ts' at the given
|
|
|
|
// height. In the case that the given height is a null round, the 'prev' flag
|
|
|
|
// selects the tipset before the null round if true, and the tipset following
|
|
|
|
// the null round if false.
|
|
|
|
func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, ts *types.TipSet, prev bool) (*types.TipSet, error) {
|
2019-09-18 03:25:12 +00:00
|
|
|
if ts == nil {
|
|
|
|
ts = cs.GetHeaviestTipSet()
|
|
|
|
}
|
|
|
|
|
|
|
|
if h > ts.Height() {
|
2020-06-04 01:25:41 +00:00
|
|
|
return nil, xerrors.Errorf("looking for tipset with height greater than start point")
|
2019-09-18 03:25:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 23:36:54 +00:00
|
|
|
if h == ts.Height() {
|
|
|
|
return ts, nil
|
|
|
|
}
|
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
lbts, err := cs.cindex.GetTipsetByHeight(ctx, ts, h)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-19 15:53:00 +00:00
|
|
|
|
2020-06-04 21:56:57 +00:00
|
|
|
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)
|
2019-09-18 03:25:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-06-04 21:56:57 +00:00
|
|
|
}
|
2019-11-19 15:53:00 +00:00
|
|
|
|
2020-06-04 00:14:36 +00:00
|
|
|
if lbts.Height() == h || !prev {
|
|
|
|
return lbts, nil
|
2019-09-18 03:25:12 +00:00
|
|
|
}
|
2020-06-04 00:14:36 +00:00
|
|
|
|
|
|
|
return cs.LoadTipSet(lbts.Parents())
|
2019-09-18 03:25:12 +00:00
|
|
|
}
|