lotus/chain/sync.go

672 lines
16 KiB
Go
Raw Normal View History

2019-07-05 14:29:17 +00:00
package chain
import (
"context"
"fmt"
"sync"
2019-07-12 23:52:25 +00:00
"github.com/filecoin-project/go-lotus/chain/actors"
2019-07-26 04:54:22 +00:00
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
2019-07-26 04:54:22 +00:00
"github.com/filecoin-project/go-lotus/chain/vm"
2019-07-08 12:51:45 +00:00
2019-07-05 14:29:17 +00:00
"github.com/ipfs/go-cid"
dstore "github.com/ipfs/go-datastore"
"github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
2019-07-26 04:54:22 +00:00
logging "github.com/ipfs/go-log"
2019-07-08 12:51:45 +00:00
peer "github.com/libp2p/go-libp2p-core/peer"
2019-07-05 14:29:17 +00:00
"github.com/pkg/errors"
"github.com/whyrusleeping/sharray"
)
2019-07-26 04:54:22 +00:00
const ForkLengthThreshold = 20
var log = logging.Logger("chain")
2019-07-05 14:29:17 +00:00
type Syncer struct {
// The heaviest known tipset in the network.
2019-07-26 04:54:22 +00:00
head *types.TipSet
2019-07-05 14:29:17 +00:00
// 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-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
// the current mode the syncer is in
syncMode SyncMode
syncLock sync.Mutex
// TipSets known to be invalid
bad BadTipSetCache
// handle to the block sync service
2019-07-08 14:07:09 +00:00
Bsync *BlockSync
2019-07-05 14:29:17 +00:00
2019-07-11 02:36:43 +00:00
self peer.ID
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-07-26 04:54:22 +00:00
func NewSyncer(cs *store.ChainStore, bsync *BlockSync, self peer.ID) (*Syncer, error) {
2019-07-05 14:29:17 +00:00
gen, err := cs.GetGenesis()
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{
syncMode: Bootstrap,
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-07-05 14:29:17 +00:00
head: cs.GetHeaviestTipSet(),
store: cs,
2019-07-11 02:36:43 +00:00
self: self,
2019-07-05 14:29:17 +00:00
}, nil
}
type SyncMode int
const (
Unknown = SyncMode(iota)
Bootstrap
CaughtUp
)
type BadTipSetCache struct {
badBlocks map[cid.Cid]struct{}
}
type BlockSet struct {
2019-07-26 04:54:22 +00:00
tset map[uint64]*types.TipSet
head *types.TipSet
2019-07-05 14:29:17 +00:00
}
2019-07-26 04:54:22 +00:00
func (bs *BlockSet) Insert(ts *types.TipSet) {
2019-07-05 14:29:17 +00:00
if bs.tset == nil {
2019-07-26 04:54:22 +00:00
bs.tset = make(map[uint64]*types.TipSet)
2019-07-05 14:29:17 +00:00
}
if bs.head == nil || ts.Height() > bs.head.Height() {
bs.head = ts
}
bs.tset[ts.Height()] = ts
}
2019-07-26 04:54:22 +00:00
func (bs *BlockSet) GetByHeight(h uint64) *types.TipSet {
2019-07-05 14:29:17 +00:00
return bs.tset[h]
}
2019-07-26 04:54:22 +00:00
func (bs *BlockSet) PersistTo(cs *store.ChainStore) error {
2019-07-05 14:29:17 +00:00
for _, ts := range bs.tset {
for _, b := range ts.Blocks() {
2019-07-26 04:54:22 +00:00
if err := cs.PersistBlockHeader(b); err != nil {
2019-07-05 14:29:17 +00:00
return err
}
}
}
return nil
}
2019-07-26 04:54:22 +00:00
func (bs *BlockSet) Head() *types.TipSet {
2019-07-05 14:29:17 +00:00
return bs.head
}
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-07-05 14:29:17 +00:00
if fts == nil {
panic("bad")
}
2019-07-11 02:36:43 +00:00
if from == syncer.self {
// TODO: this is kindof a hack...
log.Infof("got block from ourselves")
syncer.syncLock.Lock()
defer syncer.syncLock.Unlock()
if syncer.syncMode == Bootstrap {
syncer.syncMode = CaughtUp
}
if err := syncer.SyncCaughtUp(fts); err != nil {
log.Errorf("failed to sync our own block: %s", err)
}
return
}
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
go func() {
syncer.syncLock.Lock()
defer syncer.syncLock.Unlock()
switch syncer.syncMode {
case Bootstrap:
syncer.SyncBootstrap()
case CaughtUp:
if err := syncer.SyncCaughtUp(fts); err != nil {
log.Errorf("sync error: %s", err)
}
case Unknown:
panic("invalid syncer state")
}
}()
}
func (syncer *Syncer) GetPeers() []peer.ID {
syncer.peerHeadsLk.Lock()
defer syncer.peerHeadsLk.Unlock()
var out []peer.ID
for p, _ := range syncer.peerHeads {
out = append(out, p)
}
return out
}
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)
}
// SyncBootstrap is used to synchronise your chain when first joining
// the network, or when rejoining after significant downtime.
func (syncer *Syncer) SyncBootstrap() {
fmt.Println("Sync bootstrap!")
defer fmt.Println("bye bye sync bootstrap")
ctx := context.Background()
if syncer.syncMode == CaughtUp {
log.Errorf("Called SyncBootstrap while in caught up mode")
return
}
selectedHead, err := syncer.selectHead(syncer.peerHeads)
if err != nil {
log.Error("failed to select head: ", err)
return
}
2019-07-26 04:54:22 +00:00
blockSet := []*types.TipSet{selectedHead}
2019-07-05 14:29:17 +00:00
cur := selectedHead.Cids()
2019-07-11 02:36:43 +00:00
// If, for some reason, we have a suffix of the chain locally, handle that here
for blockSet[len(blockSet)-1].Height() > 0 {
log.Errorf("syncing local: ", cur)
ts, err := syncer.store.LoadTipSet(cur)
if err != nil {
if err == bstore.ErrNotFound {
log.Error("not found: ", cur)
break
}
log.Errorf("loading local tipset: %s", err)
return
}
blockSet = append(blockSet, ts)
cur = ts.Parents()
}
for blockSet[len(blockSet)-1].Height() > 0 {
2019-07-05 14:29:17 +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
fmt.Println("Get blocks: ", cur)
2019-07-08 14:07:09 +00:00
blks, err := syncer.Bsync.GetBlocks(context.TODO(), cur, 10)
2019-07-05 14:29:17 +00:00
if err != nil {
log.Error("failed to get blocks: ", err)
return
}
for _, b := range blks {
blockSet = append(blockSet, b)
}
cur = blks[len(blks)-1].Parents()
}
// hacks. in the case that we request X blocks starting at height X+1, we
2019-07-05 14:46:21 +00:00
// won't get the Genesis block in the returned blockset. This hacks around it
2019-07-05 14:29:17 +00:00
if blockSet[len(blockSet)-1].Height() != 0 {
2019-07-05 14:46:21 +00:00
blockSet = append(blockSet, syncer.Genesis)
2019-07-05 14:29:17 +00:00
}
blockSet = reverse(blockSet)
genesis := blockSet[0]
2019-07-05 14:46:21 +00:00
if !genesis.Equals(syncer.Genesis) {
2019-07-05 14:29:17 +00:00
// TODO: handle this...
2019-07-05 14:46:21 +00:00
log.Errorf("We synced to the wrong chain! %s != %s", genesis, syncer.Genesis)
2019-07-05 14:29:17 +00:00
return
}
for _, ts := range blockSet {
for _, b := range ts.Blocks() {
2019-07-26 04:54:22 +00:00
if err := syncer.store.PersistBlockHeader(b); err != nil {
2019-07-05 14:29:17 +00:00
log.Errorf("failed to persist synced blocks to the chainstore: %s", err)
return
}
}
}
// Fetch all the messages for all the blocks in this chain
windowSize := uint64(10)
for i := uint64(0); i <= selectedHead.Height(); i += windowSize {
bs := bstore.NewBlockstore(dstore.NewMapDatastore())
cst := hamt.CSTFromBstore(bs)
nextHeight := i + windowSize - 1
if nextHeight > selectedHead.Height() {
nextHeight = selectedHead.Height()
}
next := blockSet[nextHeight]
2019-07-08 14:07:09 +00:00
bstips, err := syncer.Bsync.GetChainMessages(ctx, next, (nextHeight+1)-i)
2019-07-05 14:29:17 +00:00
if err != nil {
log.Errorf("failed to fetch messages: %s", err)
return
}
for bsi := 0; bsi < len(bstips); bsi++ {
cur := blockSet[i+uint64(bsi)]
bstip := bstips[len(bstips)-(bsi+1)]
fmt.Println("that loop: ", bsi, len(bstips))
fts, err := zipTipSetAndMessages(cst, cur, bstip.Messages, bstip.MsgIncludes)
if err != nil {
log.Error("zipping failed: ", err, bsi, i)
log.Error("height: ", selectedHead.Height())
log.Error("bstips: ", bstips)
log.Error("next height: ", nextHeight)
return
}
if err := syncer.ValidateTipSet(context.TODO(), fts); err != nil {
2019-07-05 14:29:17 +00:00
log.Errorf("failed to validate tipset: %s", err)
return
}
}
for _, bst := range bstips {
for _, m := range bst.Messages {
if _, err := cst.Put(context.TODO(), m); err != nil {
log.Error("failed to persist messages: ", err)
return
}
}
}
2019-07-26 04:54:22 +00:00
if err := copyBlockstore(bs, syncer.store.Blockstore()); err != nil {
2019-07-05 14:29:17 +00:00
log.Errorf("failed to persist temp blocks: %s", err)
return
}
}
head := blockSet[len(blockSet)-1]
log.Errorf("Finished syncing! new head: %s", head.Cids())
if err := syncer.store.MaybeTakeHeavierTipSet(selectedHead); err != nil {
log.Errorf("MaybeTakeHeavierTipSet failed: %s", err)
}
2019-07-05 14:29:17 +00:00
syncer.head = head
syncer.syncMode = CaughtUp
}
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-07-26 04:54:22 +00:00
func zipTipSetAndMessages(cst *hamt.CborIpldStore, ts *types.TipSet, messages []*types.SignedMessage, msgincl [][]int) (*store.FullTipSet, error) {
2019-07-05 14:29:17 +00:00
if len(ts.Blocks()) != len(msgincl) {
return nil, fmt.Errorf("msgincl length didnt match tipset size")
}
fmt.Println("zipping messages: ", msgincl)
fmt.Println("into block: ", ts.Blocks()[0].Height)
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() {
var msgs []*types.SignedMessage
2019-07-05 14:29:17 +00:00
var msgCids []interface{}
for _, m := range msgincl[bi] {
msgs = append(msgs, messages[m])
msgCids = append(msgCids, messages[m].Cid())
}
mroot, err := sharray.Build(context.TODO(), 4, msgCids, cst)
if err != nil {
return nil, err
}
fmt.Println("messages: ", msgCids)
fmt.Println("message root: ", b.Messages, mroot)
if b.Messages != mroot {
return nil, fmt.Errorf("messages didnt match message root in header")
}
fb := &types.FullBlock{
2019-07-05 14:29:17 +00:00
Header: b,
Messages: msgs,
}
fts.Blocks = append(fts.Blocks, fb)
}
return fts, nil
}
2019-07-26 04:54:22 +00:00
func (syncer *Syncer) selectHead(heads map[peer.ID]*types.TipSet) (*types.TipSet, error) {
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
}
if sel.Height()-nca.Height() > ForkLengthThreshold {
// TODO: handle this better than refusing to sync
return nil, fmt.Errorf("Conflict exists in heads set")
}
if syncer.store.Weight(cur) > syncer.store.Weight(sel) {
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() {
messages, err := syncer.store.MessagesForBlock(b)
if err != nil {
return nil, err
}
fb := &types.FullBlock{
2019-07-05 14:29:17 +00:00
Header: b,
Messages: messages,
}
fts.Blocks = append(fts.Blocks, fb)
}
return fts, nil
}
// SyncCaughtUp is used to stay in sync once caught up to
// the rest of the network.
2019-07-26 04:54:22 +00:00
func (syncer *Syncer) SyncCaughtUp(maybeHead *store.FullTipSet) error {
2019-07-05 14:29:17 +00:00
ts := maybeHead.TipSet()
2019-07-05 14:46:21 +00:00
if syncer.Genesis.Equals(ts) {
2019-07-05 14:29:17 +00:00
return nil
}
chain, err := syncer.collectChainCaughtUp(maybeHead)
if err != nil {
return err
}
for i := len(chain) - 1; i >= 0; i-- {
ts := chain[i]
if err := syncer.ValidateTipSet(context.TODO(), ts); err != nil {
2019-07-05 14:29:17 +00:00
return errors.Wrap(err, "validate tipset failed")
}
if err := syncer.store.PutTipSet(ts); err != nil {
return errors.Wrap(err, "PutTipSet failed in SyncCaughtUp")
}
2019-07-05 14:29:17 +00:00
}
if err := syncer.store.PutTipSet(maybeHead); err != nil {
return errors.Wrap(err, "failed to put synced tipset to chainstore")
}
if syncer.store.Weight(chain[0].TipSet()) > syncer.store.Weight(syncer.head) {
fmt.Println("Accepted new head: ", chain[0].Cids())
syncer.head = chain[0].TipSet()
}
return nil
}
func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) error {
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 {
if err := syncer.ValidateBlock(ctx, b); err != nil {
2019-07-05 14:29:17 +00:00
return err
}
}
return nil
}
func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error {
2019-07-05 14:29:17 +00:00
h := b.Header
stateroot, err := syncer.store.TipSetState(h.Parents)
if err != nil {
log.Error("get tipsetstate failed: ", h.Height, h.Parents, err)
return err
}
baseTs, err := syncer.store.LoadTipSet(b.Header.Parents)
if err != nil {
return err
}
2019-07-25 22:15:33 +00:00
vmi, err := vm.NewVM(stateroot, b.Header.Height, b.Header.Miner, syncer.store)
2019-07-05 14:29:17 +00:00
if err != nil {
return err
}
2019-07-25 22:15:33 +00:00
if err := vmi.TransferFunds(actors.NetworkAddress, b.Header.Miner, vm.MiningRewardForBlock(baseTs)); err != nil {
2019-07-05 14:29:17 +00:00
return err
}
var receipts []interface{}
for _, m := range b.Messages {
2019-07-25 22:15:33 +00:00
receipt, err := vmi.ApplyMessage(ctx, &m.Message)
2019-07-05 14:29:17 +00:00
if err != nil {
return err
}
receipts = append(receipts, receipt)
}
2019-07-26 04:54:22 +00:00
cst := hamt.CSTFromBstore(syncer.store.Blockstore())
2019-07-05 14:29:17 +00:00
recptRoot, err := sharray.Build(context.TODO(), 4, receipts, cst)
if err != nil {
return err
}
if recptRoot != b.Header.MessageReceipts {
return fmt.Errorf("receipts mismatched")
}
2019-07-25 22:15:33 +00:00
final, err := vmi.Flush(context.TODO())
2019-07-05 14:29:17 +00:00
if err != nil {
return err
}
if b.Header.StateRoot != final {
return fmt.Errorf("final state root does not match block")
}
return nil
}
2019-07-26 04:54:22 +00:00
func (syncer *Syncer) Punctual(ts *types.TipSet) bool {
2019-07-05 14:29:17 +00:00
return true
}
2019-07-26 04:54:22 +00:00
func (syncer *Syncer) collectChainCaughtUp(fts *store.FullTipSet) ([]*store.FullTipSet, error) {
2019-07-05 14:29:17 +00:00
// fetch tipset and messages via bitswap
2019-07-26 04:54:22 +00:00
chain := []*store.FullTipSet{fts}
2019-07-05 14:29:17 +00:00
cur := fts.TipSet()
for {
ts, err := syncer.store.LoadTipSet(cur.Parents())
if err != nil {
// <TODO: cleanup>
// TODO: This is 'borrowed' from SyncBootstrap, needs at least some deduplicating
blockSet := []*types.TipSet{cur}
at := cur.Cids()
for blockSet[len(blockSet)-1].Height() > syncer.head.Height() {
// 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
fmt.Println("CaughtUp Get blocks")
blks, err := syncer.Bsync.GetBlocks(context.TODO(), at, 10)
if err != nil {
log.Error("failed to get blocks: ", err)
panic("aaa")
}
for _, b := range blks {
blockSet = append(blockSet, b)
}
at = blks[len(blks)-1].Parents()
}
for _, ts := range blockSet {
for _, b := range ts.Blocks() {
if err := syncer.store.PersistBlockHeader(b); err != nil {
log.Errorf("failed to persist synced blocks to the chainstore: %s", err)
panic("bbbbb")
}
}
}
// TODO: Message processing?
//log.Errorf("dont have parent blocks for sync tipset: %s", err)
//panic("should do something better, like fetch? or error?")
ts, err = syncer.store.LoadTipSet(cur.Parents())
if err != nil {
log.Errorf("HACK DIDNT WORK :( dont have parent blocks for sync tipset: %s", err)
panic("should do something better, like fetch? or error?")
}
// </TODO>
2019-07-05 14:29:17 +00:00
}
return chain, nil // return the chain because we have this last block in our cache already.
2019-07-05 14:46:21 +00:00
if ts.Equals(syncer.Genesis) {
2019-07-05 14:29:17 +00:00
break
}
/*
if !syncer.Punctual(ts) {
syncer.bad.InvalidateChain(chain)
syncer.bad.InvalidateTipSet(ts)
return nil, errors.New("tipset forks too far back from head")
}
*/
chain = append(chain, fts)
log.Error("received unknown chain in caught up mode...")
panic("for now, we panic...")
has, err := syncer.store.Contains(ts)
if err != nil {
return nil, err
}
if has {
// Store has record of this tipset.
return chain, nil
}
/*
parent, err := syncer.FetchTipSet(context.TODO(), ts.Parents())
if err != nil {
return nil, err
}
ts = parent
*/
}
return chain, nil
2019-07-05 14:36:08 +00:00
}