Implement initial mining logic

This commit is contained in:
whyrusleeping 2019-07-10 19:36:43 -07:00
parent e09a379c3b
commit d381025ccc
14 changed files with 203 additions and 166 deletions

View File

@ -4,8 +4,8 @@ import (
"context"
"github.com/filecoin-project/go-lotus/chain"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p-core/peer"
)
@ -20,8 +20,9 @@ type Version struct {
type API interface {
// chain
ChainHead(context.Context) ([]cid.Cid, error)
ChainHead(context.Context) (*chain.TipSet, error) // TODO: check serialization
ChainSubmitBlock(ctx context.Context, blk *chain.BlockMsg) error // TODO: check serialization
ChainGetRandomness(context.Context, *chain.TipSet) ([]byte, error)
// messages
@ -30,7 +31,7 @@ type API interface {
// // status
// // mpool
// // // ls / show / rm
MpoolPending(context.Context) ([]*chain.SignedMessage, error)
MpoolPending(context.Context, *chain.TipSet) ([]*chain.SignedMessage, error)
// dag
@ -53,6 +54,8 @@ type API interface {
// // power
// // set-price
// // set-perrid
MinerStart(context.Context, address.Address) error
MinerCreateBlock(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, error)
// // UX ?

View File

@ -4,7 +4,7 @@ import (
"context"
"github.com/filecoin-project/go-lotus/chain"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/libp2p/go-libp2p-core/peer"
)
@ -15,10 +15,14 @@ type Struct struct {
ID func(context.Context) (peer.ID, error)
Version func(context.Context) (Version, error)
ChainSubmitBlock func(ctx context.Context, blk *chain.BlockMsg) error
ChainHead func(context.Context) ([]cid.Cid, error)
ChainSubmitBlock func(ctx context.Context, blk *chain.BlockMsg) error
ChainHead func(context.Context) (*chain.TipSet, error)
ChainGetRandomness func(context.Context, *chain.TipSet) ([]byte, error)
MpoolPending func(ctx context.Context) ([]*chain.SignedMessage, error)
MpoolPending func(context.Context, *chain.TipSet) ([]*chain.SignedMessage, error)
MinerStart func(context.Context, address.Address) error
MinerCreateBlock func(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, error)
NetPeers func(context.Context) ([]peer.AddrInfo, error)
NetConnect func(context.Context, peer.AddrInfo) error
@ -26,8 +30,16 @@ type Struct struct {
}
}
func (c *Struct) MpoolPending(ctx context.Context) ([]*chain.SignedMessage, error) {
return c.Internal.MpoolPending(ctx)
func (c *Struct) MpoolPending(ctx context.Context, ts *chain.TipSet) ([]*chain.SignedMessage, error) {
return c.Internal.MpoolPending(ctx, ts)
}
func (c *Struct) MinerStart(ctx context.Context, addr address.Address) error {
return c.Internal.MinerStart(ctx, addr)
}
func (c *Struct) MinerCreateBlock(ctx context.Context, addr address.Address, base *chain.TipSet, tickets []chain.Ticket, eproof chain.ElectionProof, msgs []*chain.SignedMessage) (*chain.BlockMsg, error) {
return c.Internal.MinerCreateBlock(ctx, addr, base, tickets, eproof, msgs)
}
func (c *Struct) NetPeers(ctx context.Context) ([]peer.AddrInfo, error) {
@ -46,10 +58,14 @@ func (c *Struct) ChainSubmitBlock(ctx context.Context, blk *chain.BlockMsg) erro
return c.Internal.ChainSubmitBlock(ctx, blk)
}
func (c *Struct) ChainHead(ctx context.Context) ([]cid.Cid, error) {
func (c *Struct) ChainHead(ctx context.Context) (*chain.TipSet, error) {
return c.Internal.ChainHead(ctx)
}
func (c *Struct) ChainGetRandomness(ctx context.Context, pts *chain.TipSet) ([]byte, error) {
return c.Internal.ChainGetRandomness(ctx, pts)
}
// ID implements API.ID
func (c *Struct) ID(ctx context.Context) (peer.ID, error) {
return c.Internal.ID(ctx)

View File

@ -155,6 +155,8 @@ func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error
return nil, err
}
fmt.Println("genesis block is: ", sb.Cid())
return &GenesisBootstrap{
Genesis: b,
MinerKey: minerAddr,

View File

@ -3,7 +3,6 @@ package chain
import (
"context"
"fmt"
"time"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
@ -14,154 +13,40 @@ import (
bls "github.com/filecoin-project/go-lotus/lib/bls-signatures"
)
type Miner struct {
cs *ChainStore
newBlockCB func(*FullBlock)
maddr address.Address
mpool *MessagePool
Delay time.Duration
candidate *MiningBase
}
func NewMiner(cs *ChainStore, maddr address.Address, mpool *MessagePool, newBlockCB func(*FullBlock)) *Miner {
return &Miner{
cs: cs,
newBlockCB: newBlockCB,
maddr: maddr,
mpool: mpool,
Delay: time.Second * 2,
}
}
type MiningBase struct {
ts *TipSet
tickets []Ticket
}
func (m *Miner) Mine(ctx context.Context) {
log.Error("mining...")
defer log.Error("left mining...")
for {
base := m.GetBestMiningCandidate()
b, err := m.mineOne(ctx, base)
if err != nil {
log.Error(err)
continue
}
if b != nil {
m.submitNewBlock(b)
}
}
}
func (m *Miner) GetBestMiningCandidate() *MiningBase {
best := m.cs.GetHeaviestTipSet()
if m.candidate == nil {
if best == nil {
panic("no best candidate!")
}
return &MiningBase{
ts: best,
}
}
if m.cs.Weight(best) > m.cs.Weight(m.candidate.ts) {
return &MiningBase{
ts: best,
}
}
return m.candidate
}
func (m *Miner) submitNullTicket(base *MiningBase, ticket Ticket) {
panic("nyi")
}
func (m *Miner) isWinnerNextRound(base *MiningBase) (bool, ElectionProof, error) {
return true, []byte("election prooooof"), nil
}
func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (Ticket, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(m.Delay):
}
return []byte("this is a ticket"), nil
}
func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*FullBlock, error) {
log.Info("mine one")
ticket, err := m.scratchTicket(ctx, base)
if err != nil {
return nil, errors.Wrap(err, "scratching ticket failed")
}
win, proof, err := m.isWinnerNextRound(base)
if err != nil {
return nil, errors.Wrap(err, "failed to check if we win next round")
}
if !win {
m.submitNullTicket(base, ticket)
return nil, nil
}
b, err := m.createBlock(base, ticket, proof)
if err != nil {
return nil, errors.Wrap(err, "failed to create block")
}
fmt.Println("created new block:", b.Cid())
return b, nil
}
func (m *Miner) submitNewBlock(b *FullBlock) {
if err := m.cs.AddBlock(b.Header); err != nil {
log.Error("failed to add new block to chainstore: ", err)
}
m.newBlockCB(b)
}
func miningRewardForBlock(base *TipSet) BigInt {
return NewInt(10000)
}
func (m *Miner) createBlock(base *MiningBase, ticket Ticket, proof ElectionProof) (*FullBlock, error) {
st, err := m.cs.TipSetState(base.ts.Cids())
func MinerCreateBlock(cs *ChainStore, miner address.Address, parents *TipSet, tickets []Ticket, proof ElectionProof, msgs []*SignedMessage) (*FullBlock, error) {
st, err := cs.TipSetState(parents.Cids())
if err != nil {
return nil, errors.Wrap(err, "failed to load tipset state")
}
height := base.ts.Height() + uint64(len(base.tickets)) + 1
height := parents.Height() + uint64(len(tickets))
vm, err := NewVM(st, height, m.maddr, m.cs)
vm, err := NewVM(st, height, miner, cs)
if err != nil {
return nil, err
}
// apply miner reward
if err := vm.TransferFunds(NetworkAddress, m.maddr, miningRewardForBlock(base.ts)); err != nil {
if err := vm.TransferFunds(NetworkAddress, miner, miningRewardForBlock(parents)); err != nil {
return nil, err
}
next := &BlockHeader{
Miner: m.maddr,
Parents: base.ts.Cids(),
Tickets: append(base.tickets, ticket),
Miner: miner,
Parents: parents.Cids(),
Tickets: tickets,
Height: height,
}
pending := m.mpool.Pending()
fmt.Printf("adding %d messages to block...", len(pending))
fmt.Printf("adding %d messages to block...", len(msgs))
var msgCids []cid.Cid
var blsSigs []Signature
var receipts []interface{}
for _, msg := range pending {
for _, msg := range msgs {
if msg.Signature.TypeCode() == 2 {
blsSigs = append(blsSigs, msg.Signature)
@ -169,7 +54,7 @@ func (m *Miner) createBlock(base *MiningBase, ticket Ticket, proof ElectionProof
if err != nil {
return nil, err
}
if err := m.cs.bs.Put(blk); err != nil {
if err := cs.bs.Put(blk); err != nil {
return nil, err
}
@ -185,7 +70,7 @@ func (m *Miner) createBlock(base *MiningBase, ticket Ticket, proof ElectionProof
receipts = append(receipts, rec)
}
cst := hamt.CSTFromBstore(m.cs.bs)
cst := hamt.CSTFromBstore(cs.bs)
msgroot, err := sharray.Build(context.TODO(), 4, toIfArr(msgCids), cst)
if err != nil {
return nil, err
@ -210,12 +95,12 @@ func (m *Miner) createBlock(base *MiningBase, ticket Ticket, proof ElectionProof
next.BLSAggregate = aggSig
next.StateRoot = stateRoot
pweight := m.cs.Weight(base.ts)
pweight := cs.Weight(parents)
next.ParentWeight = NewInt(pweight)
fullBlock := &FullBlock{
Header: next,
Messages: pending,
Messages: msgs,
}
return fullBlock, nil

View File

@ -37,13 +37,15 @@ type Syncer struct {
// handle to the block sync service
Bsync *BlockSync
self peer.ID
// peer heads
// Note: clear cache on disconnects
peerHeads map[peer.ID]*TipSet
peerHeadsLk sync.Mutex
}
func NewSyncer(cs *ChainStore, bsync *BlockSync) (*Syncer, error) {
func NewSyncer(cs *ChainStore, bsync *BlockSync, self peer.ID) (*Syncer, error) {
gen, err := cs.GetGenesis()
if err != nil {
return nil, err
@ -61,6 +63,7 @@ func NewSyncer(cs *ChainStore, bsync *BlockSync) (*Syncer, error) {
peerHeads: make(map[peer.ID]*TipSet),
head: cs.GetHeaviestTipSet(),
store: cs,
self: self,
}, nil
}
@ -120,6 +123,21 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *FullTipSet) {
if fts == nil {
panic("bad")
}
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
}
syncer.peerHeadsLk.Lock()
syncer.peerHeads[from] = fts.TipSet()
syncer.peerHeadsLk.Unlock()
@ -180,7 +198,25 @@ func (syncer *Syncer) SyncBootstrap() {
blockSet := []*TipSet{selectedHead}
cur := selectedHead.Cids()
for /* would be cool to have a terminating condition maybe */ {
// 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 {
// 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
@ -194,9 +230,6 @@ func (syncer *Syncer) SyncBootstrap() {
for _, b := range blks {
blockSet = append(blockSet, b)
}
if blks[len(blks)-1].Height() == 0 {
break
}
cur = blks[len(blks)-1].Parents()
}

View File

@ -26,7 +26,7 @@ var chainHeadCmd = &cli.Command{
return err
}
for _, c := range head {
for _, c := range head.Cids() {
fmt.Println(c)
}
return nil

View File

@ -47,4 +47,5 @@ var Commands = []*cli.Command{
netCmd,
versionCmd,
mpoolCmd,
minerCmd,
}

41
cli/miner.go Normal file
View File

@ -0,0 +1,41 @@
package cli
import (
"fmt"
"github.com/pkg/errors"
"gopkg.in/urfave/cli.v2"
"github.com/filecoin-project/go-lotus/chain/address"
)
var minerCmd = &cli.Command{
Name: "miner",
Usage: "Manage mining",
Subcommands: []*cli.Command{
minerStart,
},
}
var minerStart = &cli.Command{
Name: "start",
Usage: "start mining",
Action: func(cctx *cli.Context) error {
api := getApi(cctx)
ctx := reqContext(cctx)
// TODO: this address needs to be the address of an actual miner
maddr, err := address.NewIDAddress(523423423)
if err != nil {
return errors.Wrap(err, "failed to create miner address")
}
if err := api.MinerStart(ctx, maddr); err != nil {
return err
}
fmt.Println("started mining")
return nil
},
}

View File

@ -21,7 +21,7 @@ var mpoolPending = &cli.Command{
api := getApi(cctx)
ctx := reqContext(cctx)
msgs, err := api.MpoolPending(ctx)
msgs, err := api.MpoolPending(ctx, nil)
if err != nil {
return err
}

View File

@ -4,6 +4,7 @@ import (
"log"
"os"
logging "github.com/ipfs/go-log"
"gopkg.in/urfave/cli.v2"
"github.com/filecoin-project/go-lotus/api"
@ -14,6 +15,7 @@ import (
)
func main() {
logging.SetLogLevel("*", "INFO")
local := []*cli.Command{
daemon.Cmd,
}

View File

@ -20,6 +20,10 @@ var Cmd = &cli.Command{
Name: "api",
Value: ":1234",
},
&cli.BoolFlag{
Name: "bootstrap",
Usage: "start node as already bootstrapped. Useful when starting a new testnet",
},
},
Action: func(cctx *cli.Context) error {
ctx := context.Background()

View File

@ -8,36 +8,46 @@ import (
"github.com/pkg/errors"
chain "github.com/filecoin-project/go-lotus/chain"
"github.com/filecoin-project/go-lotus/chain/address"
)
var log = logging.Logger("miner")
type api interface {
SubmitNewBlock(blk *chain.BlockMsg) error
ChainSubmitBlock(context.Context, *chain.BlockMsg) error
// returns a set of messages that havent been included in the chain as of
// the given tipset
PendingMessages(base *chain.TipSet) ([]*chain.SignedMessage, error)
MpoolPending(ctx context.Context, base *chain.TipSet) ([]*chain.SignedMessage, error)
// Returns the best tipset for the miner to mine on top of.
// TODO: Not sure this feels right (including the messages api). Miners
// will likely want to have more control over exactly which blocks get
// mined on, and which messages are included.
GetBestTipset() (*chain.TipSet, error)
ChainHead(context.Context) (*chain.TipSet, error)
// returns the lookback randomness from the chain used for the election
GetChainRandomness(ts *chain.TipSet) ([]byte, error)
ChainGetRandomness(context.Context, *chain.TipSet) ([]byte, error)
// create a block
// it seems realllllly annoying to do all the actions necessary to build a
// block through the API. so, we just add the block creation to the API
// now, all the 'miner' does is check if they win, and call create block
CreateBlock(base *chain.TipSet, tickets []chain.Ticket, eproof chain.ElectionProof, msgs []*chain.SignedMessage) (*chain.BlockMsg, error)
MinerCreateBlock(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, error)
}
func NewMiner(api api, addr address.Address) *Miner {
return &Miner{
api: api,
Delay: time.Second * 4,
}
}
type Miner struct {
api api
address address.Address
// time between blocks, network parameter
Delay time.Duration
@ -59,7 +69,7 @@ func (m *Miner) Mine(ctx context.Context) {
}
if b != nil {
if err := m.api.SubmitNewBlock(b); err != nil {
if err := m.api.ChainSubmitBlock(ctx, b); err != nil {
log.Errorf("failed to submit newly mined block: %s", err)
}
}
@ -72,7 +82,7 @@ type MiningBase struct {
}
func (m *Miner) GetBestMiningCandidate() (*MiningBase, error) {
bts, err := m.api.GetBestTipset()
bts, err := m.api.ChainHead(context.TODO())
if err != nil {
return nil, err
}
@ -93,7 +103,7 @@ func (m *Miner) GetBestMiningCandidate() (*MiningBase, error) {
}
func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*chain.BlockMsg, error) {
log.Info("mine one")
log.Info("mine one on:", base.ts.Cids())
ticket, err := m.scratchTicket(ctx, base)
if err != nil {
return nil, errors.Wrap(err, "scratching ticket failed")
@ -114,6 +124,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*chain.BlockMsg,
return nil, errors.Wrap(err, "failed to create block")
}
log.Infof("created new block: %s", b.Cid())
log.Infof("new blocks parents: %s", b.Header.Parents)
return b, nil
}
@ -124,7 +135,7 @@ func (m *Miner) submitNullTicket(base *MiningBase, ticket chain.Ticket) {
}
func (m *Miner) isWinnerNextRound(base *MiningBase) (bool, chain.ElectionProof, error) {
r, err := m.api.GetChainRandomness(base.ts)
r, err := m.api.ChainGetRandomness(context.TODO(), base.ts)
if err != nil {
return false, nil, err
}
@ -146,13 +157,15 @@ func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (chain.Tick
func (m *Miner) createBlock(base *MiningBase, ticket chain.Ticket, proof chain.ElectionProof) (*chain.BlockMsg, error) {
pending, err := m.api.PendingMessages(base.ts)
pending, err := m.api.MpoolPending(context.TODO(), base.ts)
if err != nil {
return nil, errors.Wrapf(err, "failed to get pending messages")
}
msgs := m.selectMessages(pending)
// why even return this? that api call could just submit it for us
return m.api.CreateBlock(base.ts, append(base.tickets, ticket), proof, pending)
return m.api.MinerCreateBlock(context.TODO(), m.address, base.ts, append(base.tickets, ticket), proof, msgs)
}
func (m *Miner) selectMessages(msgs []*chain.SignedMessage) []*chain.SignedMessage {

View File

@ -6,8 +6,9 @@ import (
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/miner"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
pubsub "github.com/libp2p/go-libp2p-pubsub"
@ -22,6 +23,10 @@ type API struct {
}
func (a *API) ChainSubmitBlock(ctx context.Context, blk *chain.BlockMsg) error {
if err := a.Chain.AddBlock(blk.Header); err != nil {
return err
}
b, err := blk.Serialize()
if err != nil {
return err
@ -31,8 +36,13 @@ func (a *API) ChainSubmitBlock(ctx context.Context, blk *chain.BlockMsg) error {
return a.PubSub.Publish("/fil/blocks", b)
}
func (a *API) ChainHead(context.Context) ([]cid.Cid, error) {
return a.Chain.GetHeaviestTipSet().Cids(), nil
func (a *API) ChainHead(context.Context) (*chain.TipSet, error) {
return a.Chain.GetHeaviestTipSet(), nil
}
func (a *API) ChainGetRandomness(ctx context.Context, pts *chain.TipSet) ([]byte, error) {
// TODO: this needs to look back in the chain for the right random beacon value
return []byte("foo bar random"), nil
}
func (a *API) ID(context.Context) (peer.ID, error) {
@ -45,10 +55,36 @@ func (a *API) Version(context.Context) (api.Version, error) {
}, nil
}
func (a *API) MpoolPending(context.Context) ([]*chain.SignedMessage, error) {
func (a *API) MpoolPending(ctx context.Context, ts *chain.TipSet) ([]*chain.SignedMessage, error) {
// TODO: need to make sure we don't return messages that were already included in the referenced chain
// also need to accept ts == nil just fine, assume nil == chain.Head()
return a.Mpool.Pending(), nil
}
func (a *API) MinerStart(ctx context.Context, addr address.Address) error {
// hrm...
m := miner.NewMiner(a, addr)
go m.Mine(context.TODO())
return nil
}
func (a *API) MinerCreateBlock(ctx context.Context, addr address.Address, parents *chain.TipSet, tickets []chain.Ticket, proof chain.ElectionProof, msgs []*chain.SignedMessage) (*chain.BlockMsg, error) {
fblk, err := chain.MinerCreateBlock(a.Chain, addr, parents, tickets, proof, msgs)
if err != nil {
return nil, err
}
var out chain.BlockMsg
out.Header = fblk.Header
for _, msg := range fblk.Messages {
out.Messages = append(out.Messages, msg.Cid())
}
return &out, nil
}
func (a *API) NetPeers(context.Context) ([]peer.AddrInfo, error) {
conns := a.Host.Network().Conns()
out := make([]peer.AddrInfo, len(conns))

View File

@ -80,8 +80,9 @@ type Settings struct {
// type, and must be applied in correct order
invokes []fx.Option
Online bool // Online option applied
Config bool // Config option applied
Online bool // Online option applied
Bootstrap bool // Start chain syncer bootstrapped
Config bool // Config option applied
}
// Override option changes constructor for a given type