Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: thoughts on apis needed for mining process #10

Merged
merged 1 commit into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions chain/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ func (ts *TipSet) Height() uint64 {
return ts.height
}

func (ts *TipSet) Weight() uint64 {
panic("if tipsets are going to have weight on them, we need to wire that through")
}

func (ts *TipSet) Parents() []cid.Cid {
return ts.blks[0].Parents
}
Expand Down
161 changes: 161 additions & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package miner

import (
"context"
"time"

logging "github.com/ipfs/go-log"
"github.com/pkg/errors"

chain "github.com/filecoin-project/go-lotus/chain"
)

var log = logging.Logger("miner")

type api interface {
SubmitNewBlock(blk *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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be very large, probably better to do sth like iterator/paging/lazy loadinf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could return a channel here, implementing those over RPC should be easy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, when this is made into a subscription, we'll also need to subscribe to incoming blocks to keep track of which messages we care about

It's probably easier to keep it this way for now, and fix when we actually have some miner code


// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what chain head would return, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, for the most part that should be the same operation. I don't have a good enough reason for them to be different yet


// returns the lookback randomness from the chain used for the election
GetChainRandomness(ts *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)
}

type Miner struct {
api api

// 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 {
if err := m.api.SubmitNewBlock(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) {
bts, err := m.api.GetBestTipset()
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("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")
}
log.Infof("created 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) {
r, err := m.api.GetChainRandomness(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) {

pending, err := m.api.PendingMessages(base.ts)
if err != nil {
return nil, errors.Wrapf(err, "failed to get pending messages")
}

// 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)
}

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
}