2019-07-08 23:48:49 +00:00
|
|
|
package miner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-08-15 02:30:21 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"math/big"
|
2019-07-08 23:48:49 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
logging "github.com/ipfs/go-log"
|
|
|
|
"github.com/pkg/errors"
|
2019-07-26 19:01:02 +00:00
|
|
|
"go.opencensus.io/trace"
|
2019-08-15 02:30:21 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
chain "github.com/filecoin-project/go-lotus/chain"
|
2019-08-15 02:30:21 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/actors"
|
2019-07-11 02:36:43 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/address"
|
2019-08-16 04:40:59 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/gen"
|
2019-07-25 22:15:03 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/chain/types"
|
2019-08-16 00:17:09 +00:00
|
|
|
"github.com/filecoin-project/go-lotus/lib/vdf"
|
2019-07-08 23:48:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var log = logging.Logger("miner")
|
|
|
|
|
|
|
|
type api interface {
|
2019-08-15 02:30:21 +00:00
|
|
|
ChainCall(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error)
|
|
|
|
|
2019-07-11 02:36:43 +00:00
|
|
|
ChainSubmitBlock(context.Context, *chain.BlockMsg) error
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
// returns a set of messages that havent been included in the chain as of
|
|
|
|
// the given tipset
|
2019-07-26 04:54:22 +00:00
|
|
|
MpoolPending(ctx context.Context, base *types.TipSet) ([]*types.SignedMessage, error)
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
// 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-26 04:54:22 +00:00
|
|
|
ChainHead(context.Context) (*types.TipSet, error)
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
// returns the lookback randomness from the chain used for the election
|
2019-07-26 04:54:22 +00:00
|
|
|
ChainGetRandomness(context.Context, *types.TipSet) ([]byte, error)
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
// 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-08-15 02:30:21 +00:00
|
|
|
MinerCreateBlock(context.Context, address.Address, *types.TipSet, []*types.Ticket, types.ElectionProof, []*types.SignedMessage) (*chain.BlockMsg, error)
|
|
|
|
|
|
|
|
WalletSign(context.Context, address.Address, []byte) (*types.Signature, error)
|
2019-07-11 02:36:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewMiner(api api, addr address.Address) *Miner {
|
|
|
|
return &Miner{
|
2019-07-24 02:45:00 +00:00
|
|
|
api: api,
|
|
|
|
address: addr,
|
|
|
|
Delay: time.Second * 4,
|
2019-07-11 02:36:43 +00:00
|
|
|
}
|
2019-07-08 23:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Miner struct {
|
|
|
|
api api
|
|
|
|
|
2019-07-11 02:36:43 +00:00
|
|
|
address address.Address
|
|
|
|
|
2019-07-08 23:48:49 +00:00
|
|
|
// time between blocks, network parameter
|
|
|
|
Delay time.Duration
|
|
|
|
|
|
|
|
lastWork *MiningBase
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) Mine(ctx context.Context) {
|
2019-07-26 19:01:02 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "/mine")
|
|
|
|
defer span.End()
|
2019-07-08 23:48:49 +00:00
|
|
|
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 {
|
2019-07-08 23:48:49 +00:00
|
|
|
log.Errorf("failed to submit newly mined block: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type MiningBase struct {
|
2019-07-26 04:54:22 +00:00
|
|
|
ts *types.TipSet
|
2019-08-15 02:30:21 +00:00
|
|
|
tickets []*types.Ticket
|
2019-07-08 23:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) GetBestMiningCandidate() (*MiningBase, error) {
|
2019-07-11 02:36:43 +00:00
|
|
|
bts, err := m.api.ChainHead(context.TODO())
|
2019-07-08 23:48:49 +00:00
|
|
|
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) {
|
2019-07-11 04:36:10 +00:00
|
|
|
log.Info("attempting to mine a block on:", base.ts.Cids())
|
2019-07-08 23:48:49 +00:00
|
|
|
ticket, err := m.scratchTicket(ctx, base)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "scratching ticket failed")
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
win, proof, err := m.isWinnerNextRound(ctx, base)
|
2019-07-08 23:48:49 +00:00
|
|
|
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")
|
|
|
|
}
|
2019-07-11 04:36:10 +00:00
|
|
|
log.Infof("mined new block: %s", b.Cid())
|
2019-07-08 23:48:49 +00:00
|
|
|
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
func (m *Miner) submitNullTicket(base *MiningBase, ticket *types.Ticket) {
|
2019-07-08 23:48:49 +00:00
|
|
|
base.tickets = append(base.tickets, ticket)
|
|
|
|
m.lastWork = base
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
func (m *Miner) computeVRF(ctx context.Context, input []byte) ([]byte, error) {
|
|
|
|
w, err := m.getMinerWorker(ctx, m.address, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-08-16 04:40:59 +00:00
|
|
|
return gen.ComputeVRF(ctx, m.api.WalletSign, w, input)
|
2019-08-15 02:30:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
|
|
|
|
ret, err := m.api.ChainCall(ctx, &types.Message{
|
|
|
|
From: addr,
|
|
|
|
To: addr,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) isWinnerNextRound(ctx context.Context, base *MiningBase) (bool, types.ElectionProof, error) {
|
2019-07-11 02:36:43 +00:00
|
|
|
r, err := m.api.ChainGetRandomness(context.TODO(), base.ts)
|
2019-07-08 23:48:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, nil, err
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
vrfout, err := m.computeVRF(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, xerrors.Errorf("failed to compute VRF: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mpow, totpow, err := m.getPowerForTipset(ctx, m.address, base.ts)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, xerrors.Errorf("failed to check power: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return powerCmp(vrfout, mpow, totpow), 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) getPowerForTipset(ctx context.Context, maddr address.Address, ts *types.TipSet) (types.BigInt, types.BigInt, error) {
|
|
|
|
var err error
|
|
|
|
enc, err := actors.SerializeParams(&actors.PowerLookupParams{maddr})
|
|
|
|
if err != nil {
|
|
|
|
return types.EmptyInt, types.EmptyInt, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ret, err := m.api.ChainCall(ctx, &types.Message{
|
|
|
|
From: maddr,
|
|
|
|
To: actors.StorageMarketAddress,
|
|
|
|
Method: actors.SMAMethods.PowerLookup,
|
|
|
|
Params: enc,
|
|
|
|
}, ts)
|
|
|
|
if err != nil {
|
|
|
|
return types.EmptyInt, types.EmptyInt, xerrors.Errorf("failed to get miner power from chain: %w", err)
|
|
|
|
}
|
|
|
|
if ret.ExitCode != 0 {
|
|
|
|
return types.EmptyInt, types.EmptyInt, xerrors.Errorf("failed to get miner power from chain (exit code %d)", ret.ExitCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
mpow := types.BigFromBytes(ret.Return)
|
2019-07-08 23:48:49 +00:00
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
ret, err = m.api.ChainCall(ctx, &types.Message{
|
|
|
|
From: maddr,
|
|
|
|
To: actors.StorageMarketAddress,
|
|
|
|
Method: actors.SMAMethods.GetTotalStorage,
|
|
|
|
}, ts)
|
|
|
|
if err != nil {
|
|
|
|
return types.EmptyInt, types.EmptyInt, xerrors.Errorf("failed to get total power from chain: %w", err)
|
|
|
|
}
|
|
|
|
if ret.ExitCode != 0 {
|
|
|
|
return types.EmptyInt, types.EmptyInt, xerrors.Errorf("failed to get total power from chain (exit code %d)", ret.ExitCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
tpow := types.BigFromBytes(ret.Return)
|
|
|
|
|
|
|
|
return mpow, tpow, nil
|
2019-07-08 23:48:49 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
func (m *Miner) runVDF(ctx context.Context, input []byte) ([]byte, []byte, error) {
|
2019-07-08 23:48:49 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2019-08-15 02:30:21 +00:00
|
|
|
return nil, nil, ctx.Err()
|
2019-07-08 23:48:49 +00:00
|
|
|
case <-time.After(m.Delay):
|
|
|
|
}
|
|
|
|
|
2019-08-16 00:17:09 +00:00
|
|
|
return vdf.Run(input)
|
2019-08-15 02:30:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (*types.Ticket, error) {
|
|
|
|
var lastTicket *types.Ticket
|
|
|
|
if len(base.tickets) > 0 {
|
|
|
|
lastTicket = base.tickets[len(base.tickets)-1]
|
|
|
|
} else {
|
2019-08-16 00:17:09 +00:00
|
|
|
lastTicket = base.ts.MinTicket()
|
2019-08-15 02:30:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vrfOut, err := m.computeVRF(ctx, lastTicket.VDFResult)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
res, proof, err := m.runVDF(ctx, vrfOut)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &types.Ticket{
|
|
|
|
VRFProof: vrfOut,
|
|
|
|
VDFResult: res,
|
|
|
|
VDFProof: proof,
|
|
|
|
}, nil
|
2019-07-08 23:48:49 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 02:30:21 +00:00
|
|
|
func (m *Miner) createBlock(base *MiningBase, ticket *types.Ticket, proof types.ElectionProof) (*chain.BlockMsg, error) {
|
2019-07-08 23:48:49 +00:00
|
|
|
|
2019-07-11 02:36:43 +00:00
|
|
|
pending, err := m.api.MpoolPending(context.TODO(), base.ts)
|
2019-07-08 23:48:49 +00:00
|
|
|
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)
|
|
|
|
|
2019-07-08 23:48:49 +00:00
|
|
|
// 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)
|
2019-07-08 23:48:49 +00:00
|
|
|
}
|
|
|
|
|
2019-07-25 22:15:03 +00:00
|
|
|
func (m *Miner) selectMessages(msgs []*types.SignedMessage) []*types.SignedMessage {
|
2019-07-08 23:48:49 +00:00
|
|
|
// TODO: filter and select 'best' message if too many to fit in one block
|
|
|
|
return msgs
|
|
|
|
}
|