lotus/miner/miner.go

174 lines
4.4 KiB
Go
Raw Normal View History

package miner
import (
"context"
"time"
logging "github.com/ipfs/go-log"
"github.com/pkg/errors"
chain "github.com/filecoin-project/go-lotus/chain"
2019-07-11 02:36:43 +00:00
"github.com/filecoin-project/go-lotus/chain/address"
)
var log = logging.Logger("miner")
type api interface {
2019-07-11 02:36:43 +00:00
ChainSubmitBlock(context.Context, *chain.BlockMsg) error
// returns a set of messages that havent been included in the chain as of
// the given tipset
2019-07-11 02:36:43 +00:00
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.
2019-07-11 02:36:43 +00:00
ChainHead(context.Context) (*chain.TipSet, error)
// returns the lookback randomness from the chain used for the election
2019-07-11 02:36:43 +00:00
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
2019-07-11 02:36:43 +00:00
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
2019-07-11 02:36:43 +00:00
address address.Address
// time between blocks, network parameter
Delay time.Duration
lastWork *MiningBase
}
func (m *Miner) Mine(ctx context.Context) {
for {
base, err := m.GetBestMiningCandidate()
if err != nil {
log.Errorf("failed to get best mining candidate: %s", err)
continue
}
b, err := m.mineOne(ctx, base)
if err != nil {
log.Errorf("mining block failed: %s", err)
continue
}
if b != nil {
2019-07-11 02:36:43 +00:00
if err := m.api.ChainSubmitBlock(ctx, b); err != nil {
log.Errorf("failed to submit newly mined block: %s", err)
}
}
}
}
type MiningBase struct {
ts *chain.TipSet
tickets []chain.Ticket
}
func (m *Miner) GetBestMiningCandidate() (*MiningBase, error) {
2019-07-11 02:36:43 +00:00
bts, err := m.api.ChainHead(context.TODO())
if err != nil {
return nil, err
}
if m.lastWork != nil {
if m.lastWork.ts.Equals(bts) {
return m.lastWork, nil
}
if bts.Weight() <= m.lastWork.ts.Weight() {
return m.lastWork, nil
}
}
return &MiningBase{
ts: bts,
}, nil
}
func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*chain.BlockMsg, error) {
log.Info("attempting to mine a block on:", base.ts.Cids())
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")
}
log.Infof("mined new block: %s", b.Cid())
return b, nil
}
func (m *Miner) submitNullTicket(base *MiningBase, ticket chain.Ticket) {
base.tickets = append(base.tickets, ticket)
m.lastWork = base
}
func (m *Miner) isWinnerNextRound(base *MiningBase) (bool, chain.ElectionProof, error) {
2019-07-11 02:36:43 +00:00
r, err := m.api.ChainGetRandomness(context.TODO(), base.ts)
if err != nil {
return false, nil, err
}
_ = r // TODO: use this to properly compute the election proof
return true, []byte("election prooooof"), nil
}
func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (chain.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) createBlock(base *MiningBase, ticket chain.Ticket, proof chain.ElectionProof) (*chain.BlockMsg, error) {
2019-07-11 02:36:43 +00:00
pending, err := m.api.MpoolPending(context.TODO(), base.ts)
if err != nil {
return nil, errors.Wrapf(err, "failed to get pending messages")
}
2019-07-11 02:36:43 +00:00
msgs := m.selectMessages(pending)
// why even return this? that api call could just submit it for us
2019-07-11 02:36:43 +00:00
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 {
// TODO: filter and select 'best' message if too many to fit in one block
return msgs
}