initial refactor for creating tipsets with multiple parents

This commit is contained in:
whyrusleeping 2019-09-03 14:36:07 +10:00
parent 2f03ac000e
commit 6e21372ccb
11 changed files with 212 additions and 88 deletions

View File

@ -106,6 +106,7 @@ type FullNode interface {
StateMinerSectors(context.Context, address.Address) ([]*SectorInfo, error)
StateMinerProvingSet(context.Context, address.Address) ([]*SectorInfo, error)
StateMinerPower(context.Context, address.Address, *types.TipSet) (MinerPower, error)
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
PaychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error)
PaychList(context.Context) ([]address.Address, error)

View File

@ -75,9 +75,10 @@ type FullNodeStruct struct {
ClientStartDeal func(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error) `perm:"admin"`
ClientRetrieve func(ctx context.Context, order RetrievalOrder, path string) error `perm:"admin"`
StateMinerSectors func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
StateMinerProvingSet func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
StateMinerPower func(context.Context, address.Address, *types.TipSet) (MinerPower, error) `perm:"read"`
StateMinerSectors func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
StateMinerProvingSet func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
StateMinerPower func(context.Context, address.Address, *types.TipSet) (MinerPower, error) `perm:"read"`
StateMinerWorker func(context.Context, address.Address, *types.TipSet) (address.Address, error) `perm:"read"`
PaychCreate func(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error) `perm:"sign"`
PaychList func(context.Context) ([]address.Address, error) `perm:"read"`
@ -283,6 +284,10 @@ func (c *FullNodeStruct) StateMinerPower(ctx context.Context, a address.Address,
return c.Internal.StateMinerPower(ctx, a, ts)
}
func (c *FullNodeStruct) StateMinerWorker(ctx context.Context, m address.Address, ts *types.TipSet) (address.Address, error) {
return c.Internal.StateMinerWorker(ctx, m, ts)
}
func (c *FullNodeStruct) PaychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error) {
return c.Internal.PaychCreate(ctx, from, to, amt)
}

View File

@ -396,7 +396,7 @@ func (bs *BlockSync) processBlocksResponse(req *BlockSyncRequest, res *BlockSync
return nil, err
}
if !cidArrsEqual(cur.Parents(), nts.Cids()) {
if !types.CidArrsEqual(cur.Parents(), nts.Cids()) {
return nil, fmt.Errorf("parents of tipset[%d] were not tipset[%d]", bi-1, bi)
}
@ -406,18 +406,6 @@ func (bs *BlockSync) processBlocksResponse(req *BlockSyncRequest, res *BlockSync
return out, nil
}
func cidArrsEqual(a, b []cid.Cid) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if b[i] != v {
return false
}
}
return true
}
func (bs *BlockSync) GetBlock(ctx context.Context, c cid.Cid) (*types.BlockHeader, error) {
sb, err := bs.bserv.GetBlock(ctx, c)
if err != nil {

View File

@ -3,6 +3,8 @@ package gen
import (
"bytes"
"context"
"crypto/sha256"
"math/big"
"sync/atomic"
"github.com/ipfs/go-blockservice"
@ -11,6 +13,7 @@ import (
"github.com/ipfs/go-merkledag"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/store"
@ -43,8 +46,8 @@ type ChainGen struct {
w *wallet.Wallet
miner address.Address
mworker address.Address
miners []address.Address
mworkers []address.Address
receivers []address.Address
banker address.Address
bankerNonce uint64
@ -147,8 +150,8 @@ func NewGenerator() (*ChainGen, error) {
genesis: genb.Genesis,
w: w,
miner: minercfg.MinerAddr,
mworker: worker,
miners: []address.Address{minercfg.MinerAddr},
mworkers: []address.Address{worker},
banker: banker,
receivers: receievers,
@ -184,7 +187,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context) (address.Address, types.
ticks := cg.curBlock.Header.Tickets
lastTicket := ticks[len(ticks)-1]
vrfout, err := ComputeVRF(ctx, cg.w.Sign, cg.mworker, lastTicket.VDFResult)
vrfout, err := ComputeVRF(ctx, cg.w.Sign, cg.mworkers[0], lastTicket.VDFResult)
if err != nil {
return address.Undef, nil, nil, err
}
@ -200,13 +203,18 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context) (address.Address, types.
VDFResult: out,
}
return cg.miner, []byte("cat in a box"), []*types.Ticket{tick}, nil
return cg.miners[0], []byte("cat in a box"), []*types.Ticket{tick}, nil
}
func (cg *ChainGen) NextBlock() (*types.FullBlock, []*types.SignedMessage, error) {
type MinedTipSet struct {
TipSet *store.FullTipSet
Messages []*types.SignedMessage
}
func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) {
miner, proof, tickets, err := cg.nextBlockProof(context.TODO())
if err != nil {
return nil, nil, err
return nil, err
}
// make some transfers from banker
@ -229,12 +237,12 @@ func (cg *ChainGen) NextBlock() (*types.FullBlock, []*types.SignedMessage, error
unsigned, err := msg.Serialize()
if err != nil {
return nil, nil, err
return nil, err
}
sig, err := cg.w.Sign(context.TODO(), cg.banker, unsigned)
if err != nil {
return nil, nil, err
return nil, err
}
msgs[m] = &types.SignedMessage{
@ -243,7 +251,7 @@ func (cg *ChainGen) NextBlock() (*types.FullBlock, []*types.SignedMessage, error
}
if _, err := cg.cs.PutMessage(msgs[m]); err != nil {
return nil, nil, err
return nil, err
}
}
@ -251,23 +259,26 @@ func (cg *ChainGen) NextBlock() (*types.FullBlock, []*types.SignedMessage, error
parents, err := types.NewTipSet([]*types.BlockHeader{cg.curBlock.Header})
if err != nil {
return nil, nil, err
return nil, err
}
ts := parents.MinTimestamp() + (uint64(len(tickets)) * build.BlockDelay)
fblk, err := MinerCreateBlock(context.TODO(), cg.cs, cg.w, miner, parents, tickets, proof, msgs, ts)
if err != nil {
return nil, nil, err
return nil, err
}
if err := cg.cs.AddBlock(fblk.Header); err != nil {
return nil, nil, err
return nil, err
}
cg.curBlock = fblk
return fblk, msgs, nil
return &MinedTipSet{
TipSet: store.NewFullTipSet([]*types.FullBlock{fblk}),
Messages: msgs,
}, nil
}
func (cg *ChainGen) YieldRepo() (repo.Repo, error) {
@ -276,3 +287,55 @@ func (cg *ChainGen) YieldRepo() (repo.Repo, error) {
}
return cg.r, nil
}
type MiningCheckAPI interface {
ChainGetRandomness(context.Context, *types.TipSet) ([]byte, error)
StateMinerPower(context.Context, address.Address, *types.TipSet) (api.MinerPower, error)
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
WalletSign(context.Context, address.Address, []byte) (*types.Signature, error)
}
func IsRoundWinner(ctx context.Context, ts *types.TipSet, ticks []*types.Ticket, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) {
r, err := a.ChainGetRandomness(ctx, ts)
if err != nil {
return false, nil, err
}
mworker, err := a.StateMinerWorker(ctx, miner, ts)
if err != nil {
return false, nil, xerrors.Errorf("failed to get miner worker: %w", err)
}
vrfout, err := ComputeVRF(ctx, a.WalletSign, mworker, r)
if err != nil {
return false, nil, xerrors.Errorf("failed to compute VRF: %w", err)
}
pow, err := a.StateMinerPower(ctx, miner, ts)
if err != nil {
return false, nil, xerrors.Errorf("failed to check power: %w", err)
}
return PowerCmp(vrfout, pow.MinerPower, pow.TotalPower), vrfout, nil
}
func PowerCmp(vrfout []byte, mpow, totpow types.BigInt) bool {
/*
Need to check that
h(vrfout) / 2^256 < minerPower / totalPower
*/
h := sha256.Sum256(vrfout)
// 2^256
rden := types.BigInt{big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil)}
top := types.BigMul(rden, mpow)
out := types.BigDiv(top, totpow)
return types.BigCmp(types.BigFromBytes(h[:]), out) < 0
}

View File

@ -13,11 +13,11 @@ func testGeneration(t testing.TB, n int, msgs int) {
g.msgsPerBlock = msgs
for i := 0; i < n; i++ {
b, _, err := g.NextBlock()
mts, err := g.NextTipSet()
if err != nil {
t.Fatalf("error at H:%d, %s", i, err)
}
if b.Header.Height != uint64(i+1) {
if mts.TipSet.TipSet().Height() != uint64(i+1) {
t.Fatal("wrong height")
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/state"
"github.com/filecoin-project/go-lotus/chain/types"
"golang.org/x/xerrors"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
@ -35,6 +36,9 @@ type ChainStore struct {
bestTips *pubsub.PubSub
tstLk sync.Mutex
tipsets map[uint64][]cid.Cid
headChangeNotifs []func(rev, app []*types.TipSet) error
}
@ -43,6 +47,7 @@ func NewChainStore(bs bstore.Blockstore, ds dstore.Batching) *ChainStore {
bs: bs,
ds: ds,
bestTips: pubsub.New(64),
tipsets: make(map[uint64][]cid.Cid),
}
hcnf := func(rev, app []*types.TipSet) error {
@ -317,12 +322,35 @@ func (cs *ChainStore) GetHeaviestTipSet() *types.TipSet {
return cs.heaviest
}
func (cs *ChainStore) addToTipSetTracker(b *types.BlockHeader) error {
cs.tstLk.Lock()
defer cs.tstLk.Unlock()
tss := cs.tipsets[b.Height]
for _, oc := range tss {
if oc == b.Cid() {
log.Warn("tried to add block to tipset tracker that was already there")
return nil
}
}
cs.tipsets[b.Height] = append(tss, b.Cid())
// TODO: do we want to look for slashable submissions here? might as well...
return nil
}
func (cs *ChainStore) PersistBlockHeader(b *types.BlockHeader) error {
sb, err := b.ToStorageBlock()
if err != nil {
return err
}
if err := cs.addToTipSetTracker(b); err != nil {
return xerrors.Errorf("failed to insert new block (%s) into tipset tracker: %w", b.Cid(), err)
}
return cs.bs.Put(sb)
}
@ -365,12 +393,49 @@ func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) {
return PutMessage(cs.bs, m)
}
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)
}
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)
}
if types.CidArrsEqual(h.Parents, b.Parents) {
all = append(all, h)
}
}
// TODO: other validation...?
return types.NewTipSet(all)
}
func (cs *ChainStore) AddBlock(b *types.BlockHeader) error {
if err := cs.PersistBlockHeader(b); err != nil {
return err
}
ts, _ := types.NewTipSet([]*types.BlockHeader{b})
ts, err := cs.expandTipset(b)
if err != nil {
return err
}
if err := cs.MaybeTakeHeavierTipSet(ts); err != nil {
return errors.Wrap(err, "MaybeTakeHeavierTipSet failed")
}

View File

@ -562,7 +562,7 @@ func (syncer *Syncer) collectHeaders(from *types.TipSet, to *types.TipSet) ([]*t
at = blks[len(blks)-1].Parents()
}
if !cidArrsEqual(blockSet[len(blockSet)-1].Parents(), to.Cids()) {
if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents(), to.Cids()) {
// TODO: handle the case where we are on a fork and its not a simple fast forward
return nil, xerrors.Errorf("synced header chain does not link to our best block")
}

View File

@ -12,26 +12,28 @@ import (
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/chain"
"github.com/filecoin-project/go-lotus/chain/gen"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/node"
"github.com/filecoin-project/go-lotus/node/impl"
"github.com/filecoin-project/go-lotus/node/modules"
"github.com/filecoin-project/go-lotus/node/repo"
)
const source = 0
func (tu *syncTestUtil) repoWithChain(t testing.TB, h int) (repo.Repo, []byte, []*types.FullBlock) {
blks := make([]*types.FullBlock, h)
func (tu *syncTestUtil) repoWithChain(t testing.TB, h int) (repo.Repo, []byte, []*store.FullTipSet) {
blks := make([]*store.FullTipSet, h)
for i := 0; i < h; i++ {
var err error
blks[i], _, err = tu.g.NextBlock()
mts, err := tu.g.NextTipSet()
require.NoError(t, err)
fmt.Printf("block at H:%d: %s\n", blks[i].Header.Height, blks[i].Cid())
blks[i] = mts.TipSet
require.Equal(t, uint64(i+1), blks[i].Header.Height, "wrong height")
ts := mts.TipSet.TipSet()
fmt.Printf("tipset at H:%d: %s\n", ts.Height(), ts.Cids())
require.Equal(t, uint64(i+1), ts.Height(), "wrong height")
}
r, err := tu.g.YieldRepo()
@ -54,7 +56,7 @@ type syncTestUtil struct {
g *gen.ChainGen
genesis []byte
blocks []*types.FullBlock
blocks []*store.FullTipSet
nds []api.FullNode
}
@ -92,14 +94,16 @@ func (tu *syncTestUtil) Shutdown() {
}
func (tu *syncTestUtil) mineNewBlock(src int) {
fblk, msgs, err := tu.g.NextBlock()
mts, err := tu.g.NextTipSet()
require.NoError(tu.t, err)
for _, msg := range msgs {
for _, msg := range mts.Messages {
require.NoError(tu.t, tu.nds[src].MpoolPush(context.TODO(), msg))
}
require.NoError(tu.t, tu.nds[src].ChainSubmitBlock(context.TODO(), fblkToBlkMsg(fblk)))
for _, fblk := range mts.TipSet.Blocks {
require.NoError(tu.t, tu.nds[src].ChainSubmitBlock(context.TODO(), fblkToBlkMsg(fblk)))
}
}
func fblkToBlkMsg(fb *types.FullBlock) *chain.BlockMsg {
@ -215,6 +219,7 @@ func (tu *syncTestUtil) waitUntilSync(from, to int) {
}
}
/*
func (tu *syncTestUtil) submitSourceBlock(to int, h int) {
// utility to simulate incoming blocks without miner process
// TODO: should call syncer directly, this won't work correctly in all cases
@ -238,6 +243,7 @@ func (tu *syncTestUtil) submitSourceBlocks(to int, h int, n int) {
tu.submitSourceBlock(to, h+i)
}
}
*/
func TestSyncSimple(t *testing.T) {
H := 50
@ -256,7 +262,7 @@ func TestSyncSimple(t *testing.T) {
}
func TestSyncMining(t *testing.T) {
H := 100
H := 50
tu := prepSyncTest(t, H)
client := tu.addClientNode()

View File

@ -114,3 +114,15 @@ func (mm *MsgMeta) ToStorageBlock() (block.Block, error) {
return block.NewBlockWithCid(buf.Bytes(), c)
}
func CidArrsEqual(a, b []cid.Cid) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if b[i] != v {
return false
}
}
return true
}

View File

@ -2,8 +2,6 @@ package miner
import (
"context"
"crypto/sha256"
"math/big"
"sync"
"time"
@ -200,7 +198,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*chain.BlockMsg,
return nil, errors.Wrap(err, "scratching ticket failed")
}
win, proof, err := m.isWinnerNextRound(ctx, base)
win, proof, err := gen.IsRoundWinner(ctx, base.ts, base.tickets, m.addresses[0], &m.api)
if err != nil {
return nil, errors.Wrap(err, "failed to check if we win next round")
}
@ -255,43 +253,6 @@ func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *ty
return w, nil
}
func (m *Miner) isWinnerNextRound(ctx context.Context, base *MiningBase) (bool, types.ElectionProof, error) {
r, err := m.api.ChainGetRandomness(context.TODO(), base.ts)
if err != nil {
return false, nil, err
}
vrfout, err := m.computeVRF(ctx, r)
if err != nil {
return false, nil, xerrors.Errorf("failed to compute VRF: %w", err)
}
pow, err := m.api.StateMinerPower(ctx, m.addresses[0], base.ts)
if err != nil {
return false, nil, xerrors.Errorf("failed to check power: %w", err)
}
return powerCmp(vrfout, pow.MinerPower, pow.TotalPower), vrfout, nil
}
func powerCmp(vrfout []byte, mpow, totpow types.BigInt) bool {
/*
Need to check that
h(vrfout) / 2^256 < minerPower / totalPower
*/
h := sha256.Sum256(vrfout)
// 2^256
rden := types.BigInt{big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil)}
top := types.BigMul(rden, mpow)
out := types.BigDiv(top, totpow)
return types.BigCmp(types.BigFromBytes(h[:]), out) < 0
}
func (m *Miner) runVDF(ctx context.Context, input []byte) ([]byte, []byte, error) {
select {
case <-ctx.Done():

View File

@ -3,10 +3,11 @@ package full
import (
"context"
"fmt"
"strconv"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/chain/vm"
"golang.org/x/xerrors"
"strconv"
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/chain/actors"
@ -186,3 +187,25 @@ func (a *StateAPI) StateMinerPower(ctx context.Context, maddr address.Address, t
TotalPower: tpow,
}, nil
}
func (a *StateAPI) StateMinerWorker(ctx context.Context, m address.Address, ts *types.TipSet) (address.Address, error) {
ret, err := vm.Call(ctx, a.Chain, &types.Message{
From: m,
To: m,
Method: actors.MAMethods.GetWorkerAddr,
}, ts)
if err != nil {
return address.Undef, xerrors.Errorf("failed to get miner worker addr: %w", err)
}
if ret.ExitCode != 0 {
return address.Undef, xerrors.Errorf("failed to get miner worker addr (exit code %d)", ret.ExitCode)
}
w, err := address.NewFromBytes(ret.Return)
if err != nil {
return address.Undef, xerrors.Errorf("GetWorkerAddr returned malformed address: %w", err)
}
return w, nil
}