-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
|
||
// 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's what There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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