2019-07-05 14:29:17 +00:00
|
|
|
package chain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-11-06 15:11:19 +00:00
|
|
|
"errors"
|
2019-07-05 14:29:17 +00:00
|
|
|
"fmt"
|
|
|
|
"sync"
|
2019-08-30 02:59:54 +00:00
|
|
|
"time"
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-10-23 14:45:03 +00:00
|
|
|
"github.com/Gurpartap/async"
|
2019-09-17 01:56:37 +00:00
|
|
|
amt "github.com/filecoin-project/go-amt-ipld"
|
2019-11-11 19:32:30 +00:00
|
|
|
"github.com/filecoin-project/go-bls-sigs"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
2019-07-05 14:29:17 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
dstore "github.com/ipfs/go-datastore"
|
2019-09-27 23:55:15 +00:00
|
|
|
hamt "github.com/ipfs/go-hamt-ipld"
|
2019-07-05 14:29:17 +00:00
|
|
|
bstore "github.com/ipfs/go-ipfs-blockstore"
|
2019-07-26 04:54:22 +00:00
|
|
|
logging "github.com/ipfs/go-log"
|
2019-07-30 16:39:07 +00:00
|
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
2019-09-17 18:09:22 +00:00
|
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
2019-10-11 02:17:24 +00:00
|
|
|
"go.opencensus.io/trace"
|
2019-09-17 18:09:22 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-11-11 19:32:30 +00:00
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
|
|
"github.com/filecoin-project/lotus/chain/address"
|
|
|
|
"github.com/filecoin-project/lotus/chain/blocksync"
|
|
|
|
"github.com/filecoin-project/lotus/chain/state"
|
|
|
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
|
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2019-07-05 14:29:17 +00:00
|
|
|
)
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
var log = logging.Logger("chain")
|
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
type Syncer struct {
|
|
|
|
// The heaviest known tipset in the network.
|
|
|
|
|
|
|
|
// The interface for accessing and putting tipsets into local storage
|
2019-07-26 04:54:22 +00:00
|
|
|
store *store.ChainStore
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-09-06 06:26:02 +00:00
|
|
|
// the state manager handles making state queries
|
|
|
|
sm *stmgr.StateManager
|
|
|
|
|
2019-07-05 14:46:21 +00:00
|
|
|
// The known Genesis tipset
|
2019-07-26 04:54:22 +00:00
|
|
|
Genesis *types.TipSet
|
2019-07-05 14:29:17 +00:00
|
|
|
|
|
|
|
syncLock sync.Mutex
|
|
|
|
|
|
|
|
// TipSets known to be invalid
|
2019-10-09 08:50:57 +00:00
|
|
|
bad *BadBlockCache
|
2019-07-05 14:29:17 +00:00
|
|
|
|
|
|
|
// handle to the block sync service
|
2019-11-09 23:00:22 +00:00
|
|
|
Bsync *blocksync.BlockSync
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-07-11 02:36:43 +00:00
|
|
|
self peer.ID
|
|
|
|
|
2019-09-30 21:06:47 +00:00
|
|
|
syncState SyncerState
|
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
// peer heads
|
|
|
|
// Note: clear cache on disconnects
|
2019-07-26 04:54:22 +00:00
|
|
|
peerHeads map[peer.ID]*types.TipSet
|
2019-07-05 14:29:17 +00:00
|
|
|
peerHeadsLk sync.Mutex
|
|
|
|
}
|
|
|
|
|
2019-11-09 23:00:22 +00:00
|
|
|
func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, self peer.ID) (*Syncer, error) {
|
2019-09-06 06:26:02 +00:00
|
|
|
gen, err := sm.ChainStore().GetGenesis()
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
gent, err := types.NewTipSet([]*types.BlockHeader{gen})
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Syncer{
|
2019-10-09 08:50:57 +00:00
|
|
|
bad: NewBadBlockCache(),
|
2019-07-05 14:46:21 +00:00
|
|
|
Genesis: gent,
|
2019-07-08 14:07:09 +00:00
|
|
|
Bsync: bsync,
|
2019-07-26 04:54:22 +00:00
|
|
|
peerHeads: make(map[peer.ID]*types.TipSet),
|
2019-09-06 06:26:02 +00:00
|
|
|
store: sm.ChainStore(),
|
|
|
|
sm: sm,
|
2019-08-02 22:21:46 +00:00
|
|
|
self: self,
|
2019-07-05 14:29:17 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const BootstrapPeerThreshold = 1
|
|
|
|
|
|
|
|
// InformNewHead informs the syncer about a new potential tipset
|
|
|
|
// This should be called when connecting to new peers, and additionally
|
|
|
|
// when receiving new blocks from the network
|
2019-07-26 04:54:22 +00:00
|
|
|
func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) {
|
2019-10-10 11:13:26 +00:00
|
|
|
ctx := context.Background()
|
2019-07-05 14:29:17 +00:00
|
|
|
if fts == nil {
|
|
|
|
panic("bad")
|
|
|
|
}
|
2019-10-06 03:32:56 +00:00
|
|
|
|
|
|
|
for _, b := range fts.Blocks {
|
2019-10-15 12:19:10 +00:00
|
|
|
if err := syncer.ValidateMsgMeta(b); err != nil {
|
2019-10-06 03:32:56 +00:00
|
|
|
log.Warnf("invalid block received: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 02:36:43 +00:00
|
|
|
if from == syncer.self {
|
|
|
|
// TODO: this is kindof a hack...
|
2019-11-07 00:18:06 +00:00
|
|
|
log.Debug("got block from ourselves")
|
2019-07-11 02:36:43 +00:00
|
|
|
|
2019-10-10 11:13:26 +00:00
|
|
|
if err := syncer.Sync(ctx, fts.TipSet()); err != nil {
|
2019-10-10 00:38:39 +00:00
|
|
|
log.Errorf("failed to sync our own block %s: %+v", fts.TipSet().Cids(), err)
|
2019-07-11 02:36:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 20:14:40 +00:00
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
syncer.peerHeadsLk.Lock()
|
|
|
|
syncer.peerHeads[from] = fts.TipSet()
|
|
|
|
syncer.peerHeadsLk.Unlock()
|
2019-07-08 14:07:09 +00:00
|
|
|
syncer.Bsync.AddPeer(from)
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-11-09 20:14:40 +00:00
|
|
|
bestPweight := syncer.store.GetHeaviestTipSet().Blocks()[0].ParentWeight
|
2019-11-10 23:06:06 +00:00
|
|
|
targetWeight := fts.TipSet().Blocks()[0].ParentWeight
|
|
|
|
if targetWeight.LessThan(bestPweight) {
|
2019-11-09 20:14:40 +00:00
|
|
|
log.Warn("incoming tipset does not appear to be better than our best chain, ignoring for now")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
go func() {
|
2019-10-10 11:13:26 +00:00
|
|
|
if err := syncer.Sync(ctx, fts.TipSet()); err != nil {
|
2019-11-10 23:06:06 +00:00
|
|
|
log.Errorf("sync error (curW=%s, targetW=%s): %+v", bestPweight, targetWeight, err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-10-15 12:19:10 +00:00
|
|
|
func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
2019-10-06 03:32:56 +00:00
|
|
|
var bcids, scids []cbg.CBORMarshaler
|
|
|
|
for _, m := range fblk.BlsMessages {
|
|
|
|
c := cbg.CborCid(m.Cid())
|
|
|
|
bcids = append(bcids, &c)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range fblk.SecpkMessages {
|
|
|
|
c := cbg.CborCid(m.Cid())
|
|
|
|
scids = append(scids, &c)
|
|
|
|
}
|
|
|
|
|
|
|
|
bs := amt.WrapBlockstore(syncer.store.Blockstore())
|
|
|
|
smroot, err := computeMsgMeta(bs, bcids, scids)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("validating msgmeta, compute failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if fblk.Header.Messages != smroot {
|
|
|
|
return xerrors.Errorf("messages in full block did not match msgmeta root in header (%s != %s)", fblk.Header.Messages, smroot)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-14 14:21:37 +00:00
|
|
|
func (syncer *Syncer) LocalPeer() peer.ID {
|
|
|
|
return syncer.self
|
|
|
|
}
|
|
|
|
|
|
|
|
func (syncer *Syncer) ChainStore() *store.ChainStore {
|
|
|
|
return syncer.store
|
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
func (syncer *Syncer) InformNewBlock(from peer.ID, blk *types.FullBlock) {
|
2019-07-05 14:29:17 +00:00
|
|
|
// TODO: search for other blocks that could form a tipset with this block
|
|
|
|
// and then send that tipset to InformNewHead
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
fts := &store.FullTipSet{Blocks: []*types.FullBlock{blk}}
|
2019-07-05 14:29:17 +00:00
|
|
|
syncer.InformNewHead(from, fts)
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
func reverse(tips []*types.TipSet) []*types.TipSet {
|
|
|
|
out := make([]*types.TipSet, len(tips))
|
2019-07-05 14:29:17 +00:00
|
|
|
for i := 0; i < len(tips); i++ {
|
|
|
|
out[i] = tips[len(tips)-(i+1)]
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyBlockstore(from, to bstore.Blockstore) error {
|
|
|
|
cids, err := from.AllKeysChan(context.TODO())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for c := range cids {
|
|
|
|
b, err := from.Get(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := to.Put(b); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-01 20:40:47 +00:00
|
|
|
// TODO: this function effectively accepts unchecked input from the network,
|
|
|
|
// either validate it here, or ensure that its validated elsewhere (maybe make
|
|
|
|
// sure the blocksync code checks it?)
|
|
|
|
// maybe this code should actually live in blocksync??
|
2019-09-17 01:56:37 +00:00
|
|
|
func zipTipSetAndMessages(bs amt.Blocks, ts *types.TipSet, allbmsgs []*types.Message, allsmsgs []*types.SignedMessage, bmi, smi [][]uint64) (*store.FullTipSet, error) {
|
2019-08-01 20:40:47 +00:00
|
|
|
if len(ts.Blocks()) != len(smi) || len(ts.Blocks()) != len(bmi) {
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil, fmt.Errorf("msgincl length didnt match tipset size")
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
fts := &store.FullTipSet{}
|
2019-07-05 14:29:17 +00:00
|
|
|
for bi, b := range ts.Blocks() {
|
2019-08-01 20:40:47 +00:00
|
|
|
var smsgs []*types.SignedMessage
|
2019-09-17 01:56:37 +00:00
|
|
|
var smsgCids []cbg.CBORMarshaler
|
2019-08-01 20:40:47 +00:00
|
|
|
for _, m := range smi[bi] {
|
|
|
|
smsgs = append(smsgs, allsmsgs[m])
|
2019-09-17 01:56:37 +00:00
|
|
|
c := cbg.CborCid(allsmsgs[m].Cid())
|
|
|
|
smsgCids = append(smsgCids, &c)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-08-01 20:40:47 +00:00
|
|
|
var bmsgs []*types.Message
|
2019-09-17 01:56:37 +00:00
|
|
|
var bmsgCids []cbg.CBORMarshaler
|
2019-08-01 20:40:47 +00:00
|
|
|
for _, m := range bmi[bi] {
|
|
|
|
bmsgs = append(bmsgs, allbmsgs[m])
|
2019-09-17 01:56:37 +00:00
|
|
|
c := cbg.CborCid(allbmsgs[m].Cid())
|
|
|
|
bmsgCids = append(bmsgCids, &c)
|
2019-08-01 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
mrcid, err := computeMsgMeta(bs, bmsgCids, smsgCids)
|
2019-08-01 20:40:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.Messages != mrcid {
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil, fmt.Errorf("messages didnt match message root in header")
|
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
fb := &types.FullBlock{
|
2019-08-01 20:40:47 +00:00
|
|
|
Header: b,
|
|
|
|
BlsMessages: bmsgs,
|
|
|
|
SecpkMessages: smsgs,
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fts.Blocks = append(fts.Blocks, fb)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fts, nil
|
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
func computeMsgMeta(bs amt.Blocks, bmsgCids, smsgCids []cbg.CBORMarshaler) (cid.Cid, error) {
|
|
|
|
bmroot, err := amt.FromArray(bs, bmsgCids)
|
|
|
|
if err != nil {
|
|
|
|
return cid.Undef, err
|
|
|
|
}
|
|
|
|
|
|
|
|
smroot, err := amt.FromArray(bs, smsgCids)
|
|
|
|
if err != nil {
|
|
|
|
return cid.Undef, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mrcid, err := bs.Put(&types.MsgMeta{
|
|
|
|
BlsMessages: bmroot,
|
|
|
|
SecpkMessages: smroot,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return cid.Undef, xerrors.Errorf("failed to put msgmeta: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return mrcid, nil
|
|
|
|
}
|
|
|
|
|
2019-10-15 05:00:30 +00:00
|
|
|
func (syncer *Syncer) selectHead(ctx context.Context, heads map[peer.ID]*types.TipSet) (*types.TipSet, error) {
|
2019-07-26 04:54:22 +00:00
|
|
|
var headsArr []*types.TipSet
|
2019-07-05 14:29:17 +00:00
|
|
|
for _, ts := range heads {
|
|
|
|
headsArr = append(headsArr, ts)
|
|
|
|
}
|
|
|
|
|
|
|
|
sel := headsArr[0]
|
|
|
|
for i := 1; i < len(headsArr); i++ {
|
|
|
|
cur := headsArr[i]
|
|
|
|
|
|
|
|
yes, err := syncer.store.IsAncestorOf(cur, sel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if yes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
yes, err = syncer.store.IsAncestorOf(sel, cur)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if yes {
|
|
|
|
sel = cur
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
nca, err := syncer.store.NearestCommonAncestor(cur, sel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-09-03 17:45:55 +00:00
|
|
|
if sel.Height()-nca.Height() > build.ForkLengthThreshold {
|
2019-07-05 14:29:17 +00:00
|
|
|
// TODO: handle this better than refusing to sync
|
|
|
|
return nil, fmt.Errorf("Conflict exists in heads set")
|
|
|
|
}
|
|
|
|
|
2019-10-15 05:00:30 +00:00
|
|
|
curw, err := syncer.store.Weight(ctx, cur)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
selw, err := syncer.store.Weight(ctx, sel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if curw.GreaterThan(selw) {
|
2019-07-05 14:29:17 +00:00
|
|
|
sel = cur
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sel, nil
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
func (syncer *Syncer) FetchTipSet(ctx context.Context, p peer.ID, cids []cid.Cid) (*store.FullTipSet, error) {
|
2019-07-05 14:29:17 +00:00
|
|
|
if fts, err := syncer.tryLoadFullTipSet(cids); err == nil {
|
|
|
|
return fts, nil
|
|
|
|
}
|
|
|
|
|
2019-07-08 14:07:09 +00:00
|
|
|
return syncer.Bsync.GetFullTipSet(ctx, p, cids)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
func (syncer *Syncer) tryLoadFullTipSet(cids []cid.Cid) (*store.FullTipSet, error) {
|
2019-07-05 14:29:17 +00:00
|
|
|
ts, err := syncer.store.LoadTipSet(cids)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
fts := &store.FullTipSet{}
|
2019-07-05 14:29:17 +00:00
|
|
|
for _, b := range ts.Blocks() {
|
2019-08-01 20:40:47 +00:00
|
|
|
bmsgs, smsgs, err := syncer.store.MessagesForBlock(b)
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
fb := &types.FullBlock{
|
2019-08-01 20:40:47 +00:00
|
|
|
Header: b,
|
|
|
|
BlsMessages: bmsgs,
|
|
|
|
SecpkMessages: smsgs,
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
fts.Blocks = append(fts.Blocks, fb)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fts, nil
|
|
|
|
}
|
|
|
|
|
2019-10-10 11:13:26 +00:00
|
|
|
func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "chain.Sync")
|
|
|
|
defer span.End()
|
2019-11-10 23:06:06 +00:00
|
|
|
if span.IsRecordingEvents() {
|
|
|
|
span.AddAttributes(
|
|
|
|
trace.StringAttribute("tipset", fmt.Sprint(maybeHead.Cids())),
|
|
|
|
trace.Int64Attribute("height", int64(maybeHead.Height())),
|
|
|
|
)
|
|
|
|
}
|
2019-10-06 03:32:56 +00:00
|
|
|
|
2019-07-26 18:16:57 +00:00
|
|
|
syncer.syncLock.Lock()
|
|
|
|
defer syncer.syncLock.Unlock()
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
if syncer.Genesis.Equals(maybeHead) || syncer.store.GetHeaviestTipSet().Equals(maybeHead) {
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
if err := syncer.collectChain(ctx, maybeHead); err != nil {
|
2019-11-10 23:06:06 +00:00
|
|
|
span.AddAttributes(trace.StringAttribute("col_error", err.Error()))
|
2019-08-16 00:17:09 +00:00
|
|
|
return xerrors.Errorf("collectChain failed: %w", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-15 05:00:30 +00:00
|
|
|
if err := syncer.store.PutTipSet(ctx, maybeHead); err != nil {
|
2019-11-10 23:06:06 +00:00
|
|
|
span.AddAttributes(trace.StringAttribute("put_error", err.Error()))
|
2019-10-06 00:51:48 +00:00
|
|
|
return xerrors.Errorf("failed to put synced tipset to chainstore: %w", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 19:26:01 +00:00
|
|
|
func isPermanent(err error) bool {
|
|
|
|
return !errors.Is(err, ErrTemporal)
|
|
|
|
}
|
|
|
|
|
2019-07-26 19:01:02 +00:00
|
|
|
func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) error {
|
2019-10-12 09:44:56 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "validateTipSet")
|
|
|
|
defer span.End()
|
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
ts := fts.TipSet()
|
2019-07-05 14:46:21 +00:00
|
|
|
if ts.Equals(syncer.Genesis) {
|
2019-07-05 14:29:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range fts.Blocks {
|
2019-07-26 19:01:02 +00:00
|
|
|
if err := syncer.ValidateBlock(ctx, b); err != nil {
|
2019-11-06 19:26:01 +00:00
|
|
|
if isPermanent(err) {
|
2019-11-06 15:11:19 +00:00
|
|
|
syncer.bad.Add(b.Cid())
|
|
|
|
}
|
2019-09-20 02:54:52 +00:00
|
|
|
return xerrors.Errorf("validating block %s: %w", b.Cid(), err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-10-10 03:04:10 +00:00
|
|
|
|
|
|
|
if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil {
|
|
|
|
return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err)
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-16 00:17:09 +00:00
|
|
|
func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error {
|
|
|
|
var err error
|
|
|
|
enc, err := actors.SerializeParams(&actors.IsMinerParam{Addr: maddr})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-09-09 20:03:10 +00:00
|
|
|
ret, err := syncer.sm.Call(ctx, &types.Message{
|
2019-10-19 08:06:41 +00:00
|
|
|
To: actors.StoragePowerAddress,
|
2019-08-16 00:17:09 +00:00
|
|
|
From: maddr,
|
2019-10-11 22:59:36 +00:00
|
|
|
Method: actors.SPAMethods.IsMiner,
|
2019-08-16 00:17:09 +00:00
|
|
|
Params: enc,
|
|
|
|
}, baseTs)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("checking if block miner is valid failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ret.ExitCode != 0 {
|
|
|
|
return xerrors.Errorf("StorageMarket.IsMiner check failed (exit code %d)", ret.ExitCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: ensure the miner is currently not late on their PoSt submission (this hasnt landed in the spec yet)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (syncer *Syncer) validateTickets(ctx context.Context, mworker address.Address, tickets []*types.Ticket, base *types.TipSet) error {
|
2019-10-17 00:49:43 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "validateTickets")
|
|
|
|
defer span.End()
|
2019-11-12 20:52:40 +00:00
|
|
|
span.AddAttributes(trace.Int64Attribute("tickets", int64(len(tickets))))
|
2019-10-17 00:49:43 +00:00
|
|
|
|
2019-08-16 00:17:09 +00:00
|
|
|
if len(tickets) == 0 {
|
|
|
|
return xerrors.Errorf("block had no tickets")
|
|
|
|
}
|
|
|
|
|
|
|
|
cur := base.MinTicket()
|
|
|
|
for i := 0; i < len(tickets); i++ {
|
|
|
|
next := tickets[i]
|
|
|
|
|
|
|
|
sig := &types.Signature{
|
|
|
|
Type: types.KTBLS,
|
|
|
|
Data: next.VRFProof,
|
|
|
|
}
|
|
|
|
|
2019-10-09 04:38:59 +00:00
|
|
|
// TODO: ticket signatures should also include miner address
|
|
|
|
if err := sig.Verify(mworker, cur.VRFProof); err != nil {
|
2019-08-16 00:17:09 +00:00
|
|
|
return xerrors.Errorf("invalid ticket, VRFProof invalid: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cur = next
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 15:11:19 +00:00
|
|
|
var ErrTemporal = errors.New("temporal error")
|
|
|
|
|
2019-08-16 00:17:09 +00:00
|
|
|
// Should match up with 'Semantical Validation' in validation.md in the spec
|
2019-07-26 19:01:02 +00:00
|
|
|
func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error {
|
2019-10-12 09:44:56 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "validateBlock")
|
|
|
|
defer span.End()
|
2019-10-10 11:13:26 +00:00
|
|
|
|
2019-07-05 14:29:17 +00:00
|
|
|
h := b.Header
|
2019-10-02 20:03:27 +00:00
|
|
|
|
|
|
|
baseTs, err := syncer.store.LoadTipSet(h.Parents)
|
|
|
|
if err != nil {
|
2019-10-06 03:32:56 +00:00
|
|
|
return xerrors.Errorf("load parent tipset failed (%s): %w", h.Parents, err)
|
2019-10-02 20:03:27 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 19:26:14 +00:00
|
|
|
// fast checks first
|
|
|
|
if h.Timestamp > uint64(time.Now().Unix()+build.AllowableClockDrift) {
|
|
|
|
return xerrors.Errorf("block was from the future")
|
|
|
|
}
|
|
|
|
|
|
|
|
if h.Timestamp < baseTs.MinTimestamp()+uint64(build.BlockDelay*len(h.Tickets)) {
|
|
|
|
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), len(h.Tickets))
|
|
|
|
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * tkts.len:%d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, len(h.Tickets))
|
|
|
|
}
|
|
|
|
|
|
|
|
winnerCheck := async.Err(func() error {
|
|
|
|
mpow, tpow, err := stmgr.GetPower(ctx, syncer.sm, baseTs, h.Miner)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed getting power: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !types.PowerCmp(h.ElectionProof, mpow, tpow) {
|
|
|
|
return xerrors.Errorf("miner created a block but was not a winner")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
msgsCheck := async.Err(func() error {
|
|
|
|
if err := syncer.checkBlockMessages(ctx, b, baseTs); err != nil {
|
|
|
|
return xerrors.Errorf("block had invalid messages: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
minerCheck := async.Err(func() error {
|
|
|
|
if err := syncer.minerIsValid(ctx, h.Miner, baseTs); err != nil {
|
|
|
|
return xerrors.Errorf("minerIsValid failed: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// Stuff that needs stateroot / worker address
|
2019-10-10 11:13:26 +00:00
|
|
|
stateroot, precp, err := syncer.sm.TipSetState(ctx, baseTs)
|
2019-07-05 14:29:17 +00:00
|
|
|
if err != nil {
|
2019-08-02 22:21:46 +00:00
|
|
|
return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err)
|
2019-08-30 02:59:54 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
if stateroot != h.ParentStateRoot {
|
2019-11-07 22:55:24 +00:00
|
|
|
msgs, err := syncer.store.MessagesForTipset(baseTs)
|
2019-10-23 14:45:03 +00:00
|
|
|
if err != nil {
|
2019-11-07 22:55:24 +00:00
|
|
|
log.Error("failed to load messages for tipset during tipset state mismatch error: ", err)
|
|
|
|
} else {
|
|
|
|
log.Warn("Messages for tipset with mismatching state:")
|
|
|
|
for i, m := range msgs {
|
|
|
|
mm := m.VMMessage()
|
|
|
|
log.Warnf("Message[%d]: from=%s to=%s method=%d params=%x", i, mm.From, mm.To, mm.Method, mm.Params)
|
|
|
|
}
|
2019-10-23 14:45:03 +00:00
|
|
|
}
|
2019-08-16 00:17:09 +00:00
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
return xerrors.Errorf("parent state root did not match computed state (%s != %s)", stateroot, h.ParentStateRoot)
|
|
|
|
}
|
2019-08-16 00:17:09 +00:00
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
if precp != h.ParentMessageReceipts {
|
|
|
|
return xerrors.Errorf("parent receipts root did not match computed value (%s != %s)", precp, h.ParentMessageReceipts)
|
|
|
|
}
|
2019-09-11 20:10:29 +00:00
|
|
|
|
2019-10-23 17:39:14 +00:00
|
|
|
waddr, err := stmgr.GetMinerWorkerRaw(ctx, syncer.sm, stateroot, h.Miner)
|
2019-09-06 06:26:02 +00:00
|
|
|
if err != nil {
|
2019-10-23 17:39:14 +00:00
|
|
|
return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err)
|
2019-09-06 06:26:02 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 19:26:14 +00:00
|
|
|
blockSigCheck := async.Err(func() error {
|
|
|
|
if err := h.CheckBlockSignature(ctx, waddr); err != nil {
|
|
|
|
return xerrors.Errorf("check block signature failed: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2019-10-23 14:45:03 +00:00
|
|
|
|
2019-11-11 19:26:14 +00:00
|
|
|
tktsCheck := async.Err(func() error {
|
|
|
|
if err := syncer.validateTickets(ctx, waddr, h.Tickets, baseTs); err != nil {
|
|
|
|
return xerrors.Errorf("validating block tickets failed: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2019-10-23 14:45:03 +00:00
|
|
|
|
2019-11-11 19:26:14 +00:00
|
|
|
eproofCheck := async.Err(func() error {
|
|
|
|
rand, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), h.Tickets, build.EcRandomnessLookback)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to get randomness for verifying election proof: %w", err)
|
|
|
|
}
|
2019-10-23 14:45:03 +00:00
|
|
|
|
2019-11-11 19:26:14 +00:00
|
|
|
if err := VerifyElectionProof(ctx, h.ElectionProof, rand, waddr); err != nil {
|
|
|
|
return xerrors.Errorf("checking eproof failed: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2019-10-23 14:45:03 +00:00
|
|
|
|
|
|
|
await := []async.ErrorFuture{
|
|
|
|
minerCheck,
|
|
|
|
tktsCheck,
|
|
|
|
blockSigCheck,
|
|
|
|
eproofCheck,
|
|
|
|
winnerCheck,
|
|
|
|
msgsCheck,
|
|
|
|
}
|
2019-11-11 19:30:49 +00:00
|
|
|
|
|
|
|
var merr error
|
2019-10-23 14:45:03 +00:00
|
|
|
for _, fut := range await {
|
|
|
|
if err := fut.AwaitContext(ctx); err != nil {
|
2019-11-14 18:41:40 +00:00
|
|
|
merr = multierror.Append(merr, err)
|
2019-10-23 14:45:03 +00:00
|
|
|
}
|
2019-10-14 03:28:19 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 19:30:49 +00:00
|
|
|
return merr
|
2019-10-14 03:28:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error {
|
2019-09-27 23:55:15 +00:00
|
|
|
nonces := make(map[address.Address]uint64)
|
|
|
|
balances := make(map[address.Address]types.BigInt)
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-10-14 03:28:19 +00:00
|
|
|
stateroot, _, err := syncer.sm.TipSetState(ctx, baseTs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
cst := hamt.CSTFromBstore(syncer.store.Blockstore())
|
|
|
|
st, err := state.LoadStateTree(cst, stateroot)
|
2019-09-26 22:33:38 +00:00
|
|
|
if err != nil {
|
2019-09-27 23:55:15 +00:00
|
|
|
return xerrors.Errorf("failed to load base state tree: %w", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
checkMsg := func(m *types.Message) error {
|
2019-10-14 03:28:19 +00:00
|
|
|
if m.To == address.Undef {
|
|
|
|
return xerrors.New("'To' address cannot be empty")
|
|
|
|
}
|
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
if _, ok := nonces[m.From]; !ok {
|
|
|
|
act, err := st.GetActor(m.From)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to get actor: %w", err)
|
|
|
|
}
|
|
|
|
nonces[m.From] = act.Nonce
|
|
|
|
balances[m.From] = act.Balance
|
2019-08-01 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
if nonces[m.From] != m.Nonce {
|
2019-10-06 00:51:48 +00:00
|
|
|
return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[m.From], m.Nonce)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-09-27 23:55:15 +00:00
|
|
|
nonces[m.From]++
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
if balances[m.From].LessThan(m.RequiredFunds()) {
|
|
|
|
return xerrors.Errorf("not enough funds for message execution")
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-09-27 23:55:15 +00:00
|
|
|
balances[m.From] = types.BigSub(balances[m.From], m.RequiredFunds())
|
|
|
|
return nil
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
bs := amt.WrapBlockstore(syncer.store.Blockstore())
|
|
|
|
var blsCids []cbg.CBORMarshaler
|
2019-10-09 02:58:49 +00:00
|
|
|
var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type
|
|
|
|
|
|
|
|
var pubks []bls.PublicKey
|
2019-09-27 23:55:15 +00:00
|
|
|
for i, m := range b.BlsMessages {
|
|
|
|
if err := checkMsg(m); err != nil {
|
2019-10-06 03:32:56 +00:00
|
|
|
return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err)
|
2019-09-27 23:55:15 +00:00
|
|
|
}
|
2019-10-09 02:58:49 +00:00
|
|
|
|
|
|
|
sigCids = append(sigCids, m.Cid())
|
2019-10-06 03:32:56 +00:00
|
|
|
c := cbg.CborCid(m.Cid())
|
|
|
|
blsCids = append(blsCids, &c)
|
2019-10-09 02:58:49 +00:00
|
|
|
|
|
|
|
pubk, err := syncer.sm.GetBlsPublicKey(ctx, m.From, baseTs)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to load bls public to validate block: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pubks = append(pubks, pubk)
|
2019-10-06 03:32:56 +00:00
|
|
|
}
|
2019-10-09 02:58:49 +00:00
|
|
|
|
2019-10-13 09:08:34 +00:00
|
|
|
if err := syncer.verifyBlsAggregate(ctx, b.Header.BLSAggregate, sigCids, pubks); err != nil {
|
2019-10-09 02:58:49 +00:00
|
|
|
return xerrors.Errorf("bls aggregate signature was invalid: %w", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
var secpkCids []cbg.CBORMarshaler
|
2019-09-27 23:55:15 +00:00
|
|
|
for i, m := range b.SecpkMessages {
|
|
|
|
if err := checkMsg(&m.Message); err != nil {
|
2019-10-06 03:32:56 +00:00
|
|
|
return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err)
|
2019-09-27 23:55:15 +00:00
|
|
|
}
|
2019-10-09 02:58:49 +00:00
|
|
|
|
|
|
|
kaddr, err := syncer.sm.ResolveToKeyAddress(ctx, m.Message.From, baseTs)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to resolve key addr: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := m.Signature.Verify(kaddr, m.Message.Cid().Bytes()); err != nil {
|
|
|
|
return xerrors.Errorf("secpk message %s has invalid signature: %w", m.Cid(), err)
|
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
c := cbg.CborCid(m.Cid())
|
|
|
|
secpkCids = append(secpkCids, &c)
|
|
|
|
}
|
2019-10-09 02:58:49 +00:00
|
|
|
|
|
|
|
bmroot, err := amt.FromArray(bs, blsCids)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to build amt from bls msg cids: %w", err)
|
|
|
|
}
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
smroot, err := amt.FromArray(bs, secpkCids)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to build amt from bls msg cids: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mrcid, err := bs.Put(&types.MsgMeta{
|
|
|
|
BlsMessages: bmroot,
|
|
|
|
SecpkMessages: smroot,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-14 03:28:19 +00:00
|
|
|
if b.Header.Messages != mrcid {
|
2019-10-06 03:32:56 +00:00
|
|
|
return fmt.Errorf("messages didnt match message root in header")
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-13 09:08:34 +00:00
|
|
|
func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig types.Signature, msgs []cid.Cid, pubks []bls.PublicKey) error {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "syncer.verifyBlsAggregate")
|
|
|
|
defer span.End()
|
|
|
|
span.AddAttributes(
|
|
|
|
trace.Int64Attribute("msgCount", int64(len(msgs))),
|
|
|
|
)
|
|
|
|
|
2019-10-09 02:58:49 +00:00
|
|
|
var digests []bls.Digest
|
|
|
|
for _, c := range msgs {
|
|
|
|
digests = append(digests, bls.Hash(bls.Message(c.Bytes())))
|
|
|
|
}
|
|
|
|
|
|
|
|
var bsig bls.Signature
|
|
|
|
copy(bsig[:], sig.Data)
|
2019-10-11 03:26:54 +00:00
|
|
|
if !bls.Verify(&bsig, digests, pubks) {
|
2019-10-09 02:58:49 +00:00
|
|
|
return xerrors.New("bls aggregate signature failed to verify")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
2019-10-12 09:44:56 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "collectHeaders")
|
|
|
|
defer span.End()
|
2019-10-13 01:05:43 +00:00
|
|
|
|
|
|
|
span.AddAttributes(
|
|
|
|
trace.Int64Attribute("fromHeight", int64(from.Height())),
|
|
|
|
trace.Int64Attribute("toHeight", int64(to.Height())),
|
|
|
|
)
|
2019-10-12 09:44:56 +00:00
|
|
|
|
2019-11-10 23:06:06 +00:00
|
|
|
for _, pcid := range from.Parents() {
|
|
|
|
if syncer.bad.Has(pcid) {
|
|
|
|
for _, b := range from.Cids() {
|
|
|
|
syncer.bad.Add(b)
|
|
|
|
}
|
|
|
|
return nil, xerrors.Errorf("chain linked to block marked previously as bad (%s, %s)", from.Cids(), pcid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
blockSet := []*types.TipSet{from}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
at := from.Parents()
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-08-02 22:21:46 +00:00
|
|
|
// we want to sync all the blocks until the height above the block we have
|
|
|
|
untilHeight := to.Height() + 1
|
|
|
|
|
2019-10-23 08:18:07 +00:00
|
|
|
syncer.syncState.SetHeight(blockSet[len(blockSet)-1].Height())
|
|
|
|
|
2019-11-11 22:37:34 +00:00
|
|
|
var acceptedBlocks []cid.Cid
|
|
|
|
|
2019-10-23 08:18:07 +00:00
|
|
|
loop:
|
2019-08-02 22:21:46 +00:00
|
|
|
for blockSet[len(blockSet)-1].Height() > untilHeight {
|
2019-10-09 08:50:57 +00:00
|
|
|
for _, bc := range at {
|
|
|
|
if syncer.bad.Has(bc) {
|
2019-11-11 22:37:34 +00:00
|
|
|
for _, b := range acceptedBlocks {
|
|
|
|
syncer.bad.Add(b)
|
|
|
|
}
|
|
|
|
|
2019-10-09 08:50:57 +00:00
|
|
|
return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s)", from.Cids(), bc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-23 08:18:07 +00:00
|
|
|
// If, for some reason, we have a suffix of the chain locally, handle that here
|
2019-07-31 07:13:49 +00:00
|
|
|
ts, err := syncer.store.LoadTipSet(at)
|
2019-10-23 08:18:07 +00:00
|
|
|
if err == nil {
|
2019-11-11 22:37:34 +00:00
|
|
|
acceptedBlocks = append(acceptedBlocks, at...)
|
|
|
|
|
2019-10-23 08:18:07 +00:00
|
|
|
blockSet = append(blockSet, ts)
|
|
|
|
at = ts.Parents()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !xerrors.Is(err, bstore.ErrNotFound) {
|
2019-07-31 07:13:49 +00:00
|
|
|
log.Warn("loading local tipset: %s", err)
|
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
// NB: GetBlocks validates that the blocks are in-fact the ones we
|
|
|
|
// requested, and that they are correctly linked to eachother. It does
|
|
|
|
// not validate any state transitions
|
2019-09-26 07:22:45 +00:00
|
|
|
window := 500
|
2019-09-09 20:03:10 +00:00
|
|
|
if gap := int(blockSet[len(blockSet)-1].Height() - untilHeight); gap < window {
|
|
|
|
window = gap
|
|
|
|
}
|
2019-10-12 09:44:56 +00:00
|
|
|
blks, err := syncer.Bsync.GetBlocks(ctx, at, window)
|
2019-07-31 07:13:49 +00:00
|
|
|
if err != nil {
|
|
|
|
// Most likely our peers aren't fully synced yet, but forwarded
|
|
|
|
// new block message (ideally we'd find better peers)
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Errorf("failed to get blocks: %+v", err)
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-11-09 20:14:40 +00:00
|
|
|
span.AddAttributes(trace.StringAttribute("error", err.Error()))
|
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
// This error will only be logged above,
|
|
|
|
return nil, xerrors.Errorf("failed to get blocks: %w", err)
|
2019-07-30 13:55:36 +00:00
|
|
|
}
|
2019-09-26 07:22:45 +00:00
|
|
|
log.Info("Got blocks: ", blks[0].Height(), len(blks))
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
for _, b := range blks {
|
2019-08-02 22:21:46 +00:00
|
|
|
if b.Height() < untilHeight {
|
2019-09-06 20:03:28 +00:00
|
|
|
break loop
|
2019-08-02 22:21:46 +00:00
|
|
|
}
|
2019-10-09 08:50:57 +00:00
|
|
|
for _, bc := range b.Cids() {
|
|
|
|
if syncer.bad.Has(bc) {
|
2019-11-11 22:37:34 +00:00
|
|
|
for _, b := range acceptedBlocks {
|
|
|
|
syncer.bad.Add(b)
|
|
|
|
}
|
|
|
|
|
2019-10-09 08:50:57 +00:00
|
|
|
return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s)", from.Cids(), bc)
|
|
|
|
}
|
|
|
|
}
|
2019-07-31 07:13:49 +00:00
|
|
|
blockSet = append(blockSet, b)
|
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-11-11 22:37:34 +00:00
|
|
|
acceptedBlocks = append(acceptedBlocks, at...)
|
|
|
|
|
2019-10-02 21:12:37 +00:00
|
|
|
syncer.syncState.SetHeight(blks[len(blks)-1].Height())
|
2019-07-31 07:13:49 +00:00
|
|
|
at = blks[len(blks)-1].Parents()
|
|
|
|
}
|
2019-07-26 16:13:25 +00:00
|
|
|
|
2019-09-08 20:14:01 +00:00
|
|
|
// We have now ascertained that this is *not* a 'fast forward'
|
2019-09-03 04:36:07 +00:00
|
|
|
if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents(), to.Cids()) {
|
2019-09-08 20:14:01 +00:00
|
|
|
last := blockSet[len(blockSet)-1]
|
|
|
|
if types.CidArrsEqual(last.Parents(), to.Parents()) {
|
|
|
|
// common case: receiving a block thats potentially part of the same tipset as our best block
|
|
|
|
return blockSet, nil
|
|
|
|
}
|
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", from.Cids(), from.Height(), to.Cids(), to.Height())
|
|
|
|
fork, err := syncer.syncFork(ctx, last, to)
|
|
|
|
if err != nil {
|
2019-11-10 23:06:06 +00:00
|
|
|
if xerrors.Is(err, ErrForkTooLong) {
|
|
|
|
// TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish?
|
|
|
|
log.Warn("adding forked chain to our bad tipset cache")
|
|
|
|
for _, b := range from.Blocks() {
|
|
|
|
syncer.bad.Add(b.Cid())
|
|
|
|
}
|
|
|
|
}
|
2019-10-06 00:51:48 +00:00
|
|
|
return nil, xerrors.Errorf("failed to sync fork: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
blockSet = append(blockSet, fork...)
|
2019-07-31 07:13:49 +00:00
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
return blockSet, nil
|
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-11-10 23:06:06 +00:00
|
|
|
var ErrForkTooLong = fmt.Errorf("fork longer than threshold")
|
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
2019-10-08 00:28:13 +00:00
|
|
|
tips, err := syncer.Bsync.GetBlocks(ctx, from.Parents(), build.ForkLengthThreshold)
|
2019-10-06 00:51:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
nts, err := syncer.store.LoadTipSet(to.Parents())
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to load next local tipset: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for cur := 0; cur < len(tips); {
|
|
|
|
|
|
|
|
if nts.Equals(tips[cur]) {
|
|
|
|
return tips[:cur+1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if nts.Height() < tips[cur].Height() {
|
|
|
|
cur++
|
|
|
|
} else {
|
|
|
|
nts, err = syncer.store.LoadTipSet(nts.Parents())
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("loading next local tipset: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-10 23:06:06 +00:00
|
|
|
return nil, ErrForkTooLong
|
2019-10-06 00:51:48 +00:00
|
|
|
}
|
|
|
|
|
2019-10-10 11:13:26 +00:00
|
|
|
func (syncer *Syncer) syncMessagesAndCheckState(ctx context.Context, headers []*types.TipSet) error {
|
2019-09-30 21:06:47 +00:00
|
|
|
syncer.syncState.SetHeight(0)
|
2019-10-23 14:45:03 +00:00
|
|
|
|
2019-10-12 09:44:56 +00:00
|
|
|
return syncer.iterFullTipsets(ctx, headers, func(ctx context.Context, fts *store.FullTipSet) error {
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Debugw("validating tipset", "height", fts.TipSet().Height(), "size", len(fts.TipSet().Cids()))
|
2019-10-10 11:13:26 +00:00
|
|
|
if err := syncer.ValidateTipSet(ctx, fts); err != nil {
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Errorf("failed to validate tipset: %+v", err)
|
2019-08-02 00:13:57 +00:00
|
|
|
return xerrors.Errorf("message processing failed: %w", err)
|
|
|
|
}
|
2019-09-06 20:03:28 +00:00
|
|
|
|
2019-09-30 21:06:47 +00:00
|
|
|
syncer.syncState.SetHeight(fts.TipSet().Height())
|
|
|
|
|
2019-08-02 00:13:57 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// fills out each of the given tipsets with messages and calls the callback with it
|
2019-10-12 09:44:56 +00:00
|
|
|
func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipSet, cb func(context.Context, *store.FullTipSet) error) error {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "iterFullTipsets")
|
|
|
|
defer span.End()
|
|
|
|
|
2019-10-23 08:18:07 +00:00
|
|
|
windowSize := 200
|
|
|
|
for i := len(headers) - 1; i >= 0; {
|
|
|
|
fts, err := syncer.store.TryFillTipSet(headers[i])
|
2019-08-02 00:13:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-23 08:18:07 +00:00
|
|
|
if fts != nil {
|
|
|
|
if err := cb(ctx, fts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
i--
|
|
|
|
continue
|
2019-08-02 00:13:57 +00:00
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-08-02 00:13:57 +00:00
|
|
|
batchSize := windowSize
|
2019-08-02 22:21:46 +00:00
|
|
|
if i < batchSize {
|
|
|
|
batchSize = i
|
2019-07-30 13:55:36 +00:00
|
|
|
}
|
2019-07-26 16:13:25 +00:00
|
|
|
|
2019-08-02 22:21:46 +00:00
|
|
|
next := headers[i-batchSize]
|
2019-10-12 09:44:56 +00:00
|
|
|
bstips, err := syncer.Bsync.GetChainMessages(ctx, next, uint64(batchSize+1))
|
2019-07-31 07:13:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("message processing failed: %w", err)
|
|
|
|
}
|
2019-07-30 13:55:36 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
for bsi := 0; bsi < len(bstips); bsi++ {
|
2019-09-06 20:03:28 +00:00
|
|
|
// temp storage so we don't persist data we dont want to
|
|
|
|
ds := dstore.NewMapDatastore()
|
|
|
|
bs := bstore.NewBlockstore(ds)
|
2019-09-17 01:56:37 +00:00
|
|
|
blks := amt.WrapBlockstore(bs)
|
2019-09-06 20:03:28 +00:00
|
|
|
|
2019-08-02 22:21:46 +00:00
|
|
|
this := headers[i-bsi]
|
2019-07-31 07:13:49 +00:00
|
|
|
bstip := bstips[len(bstips)-(bsi+1)]
|
2019-09-17 01:56:37 +00:00
|
|
|
fts, err := zipTipSetAndMessages(blks, this, bstip.BlsMessages, bstip.SecpkMessages, bstip.BlsMsgIncludes, bstip.SecpkMsgIncludes)
|
2019-07-30 13:55:36 +00:00
|
|
|
if err != nil {
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Warnw("zipping failed", "error", err, "bsi", bsi, "i", i,
|
|
|
|
"height", this.Height(), "bstip-height", bstip.Blocks[0].Height,
|
|
|
|
"bstips", bstips, "next-height", i+batchSize)
|
2019-07-31 07:13:49 +00:00
|
|
|
return xerrors.Errorf("message processing failed: %w", err)
|
2019-07-30 13:55:36 +00:00
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-10-12 09:44:56 +00:00
|
|
|
if err := cb(ctx, fts); err != nil {
|
2019-08-02 00:13:57 +00:00
|
|
|
return err
|
2019-07-30 13:55:36 +00:00
|
|
|
}
|
2019-07-26 18:16:57 +00:00
|
|
|
|
2019-09-06 20:03:28 +00:00
|
|
|
if err := persistMessages(bs, bstip); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-26 16:13:25 +00:00
|
|
|
|
2019-09-06 20:03:28 +00:00
|
|
|
if err := copyBlockstore(bs, syncer.store.Blockstore()); err != nil {
|
|
|
|
return xerrors.Errorf("message processing failed: %w", err)
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-10-23 08:18:07 +00:00
|
|
|
i -= windowSize
|
2019-07-31 07:13:49 +00:00
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-11-09 23:00:22 +00:00
|
|
|
func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error {
|
2019-09-06 20:03:28 +00:00
|
|
|
for _, m := range bst.BlsMessages {
|
|
|
|
//log.Infof("putting BLS message: %s", m.Cid())
|
|
|
|
if _, err := store.PutMessage(bs, m); err != nil {
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Errorf("failed to persist messages: %+v", err)
|
2019-09-06 20:03:28 +00:00
|
|
|
return xerrors.Errorf("BLS message processing failed: %w", err)
|
2019-08-01 20:40:47 +00:00
|
|
|
}
|
2019-09-06 20:03:28 +00:00
|
|
|
}
|
|
|
|
for _, m := range bst.SecpkMessages {
|
|
|
|
if m.Signature.Type != types.KTSecp256k1 {
|
|
|
|
return xerrors.Errorf("unknown signature type on message %s: %q", m.Cid(), m.Signature.TypeCode)
|
|
|
|
}
|
|
|
|
//log.Infof("putting secp256k1 message: %s", m.Cid())
|
|
|
|
if _, err := store.PutMessage(bs, m); err != nil {
|
2019-10-03 18:20:29 +00:00
|
|
|
log.Errorf("failed to persist messages: %+v", err)
|
2019-09-06 20:03:28 +00:00
|
|
|
return xerrors.Errorf("secp256k1 message processing failed: %w", err)
|
2019-08-02 00:13:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error {
|
2019-10-12 09:44:56 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "collectChain")
|
|
|
|
defer span.End()
|
|
|
|
|
2019-10-06 03:32:56 +00:00
|
|
|
syncer.syncState.Init(syncer.store.GetHeaviestTipSet(), ts)
|
2019-09-30 21:06:47 +00:00
|
|
|
|
2019-10-06 00:51:48 +00:00
|
|
|
headers, err := syncer.collectHeaders(ctx, ts, syncer.store.GetHeaviestTipSet())
|
2019-07-31 07:13:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-11-09 20:14:40 +00:00
|
|
|
span.AddAttributes(trace.Int64Attribute("syncChainLength", int64(len(headers))))
|
|
|
|
|
2019-10-10 03:04:10 +00:00
|
|
|
if !headers[0].Equals(ts) {
|
|
|
|
log.Errorf("collectChain headers[0] should be equal to sync target. Its not: %s != %s", headers[0].Cids(), ts.Cids())
|
|
|
|
}
|
|
|
|
|
2019-09-30 22:29:40 +00:00
|
|
|
syncer.syncState.SetStage(api.StagePersistHeaders)
|
2019-09-30 21:06:47 +00:00
|
|
|
|
2019-11-12 10:18:46 +00:00
|
|
|
toPersist := make([]*types.BlockHeader, 0, len(headers)*build.BlocksPerEpoch)
|
2019-07-31 07:13:49 +00:00
|
|
|
for _, ts := range headers {
|
2019-11-12 10:18:46 +00:00
|
|
|
toPersist = append(toPersist, ts.Blocks()...)
|
|
|
|
}
|
|
|
|
if err := syncer.store.PersistBlockHeaders(toPersist...); err != nil {
|
|
|
|
return xerrors.Errorf("failed to persist synced blocks to the chainstore: %w", err)
|
2019-07-05 14:29:17 +00:00
|
|
|
}
|
2019-11-12 10:18:46 +00:00
|
|
|
toPersist = nil
|
2019-07-05 14:29:17 +00:00
|
|
|
|
2019-09-30 22:29:40 +00:00
|
|
|
syncer.syncState.SetStage(api.StageMessages)
|
2019-09-30 21:06:47 +00:00
|
|
|
|
2019-10-10 11:13:26 +00:00
|
|
|
if err := syncer.syncMessagesAndCheckState(ctx, headers); err != nil {
|
2019-08-16 00:17:09 +00:00
|
|
|
return xerrors.Errorf("collectChain syncMessages: %w", err)
|
2019-07-31 07:13:49 +00:00
|
|
|
}
|
|
|
|
|
2019-09-30 22:29:40 +00:00
|
|
|
syncer.syncState.SetStage(api.StageSyncComplete)
|
2019-11-07 00:18:06 +00:00
|
|
|
log.Debugw("new tipset", "height", ts.Height(), "tipset", types.LogCids(ts.Cids()))
|
2019-09-30 21:06:47 +00:00
|
|
|
|
2019-07-31 07:13:49 +00:00
|
|
|
return nil
|
2019-07-05 14:36:08 +00:00
|
|
|
}
|
2019-09-06 06:26:02 +00:00
|
|
|
|
|
|
|
func VerifyElectionProof(ctx context.Context, eproof []byte, rand []byte, worker address.Address) error {
|
|
|
|
sig := types.Signature{
|
|
|
|
Data: eproof,
|
|
|
|
Type: types.KTBLS,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sig.Verify(worker, rand); err != nil {
|
|
|
|
return xerrors.Errorf("failed to verify election proof signature: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-09-30 21:06:47 +00:00
|
|
|
|
|
|
|
func (syncer *Syncer) State() SyncerState {
|
|
|
|
return syncer.syncState.Snapshot()
|
|
|
|
}
|