2019-07-08 23:48:49 +00:00
package miner
import (
2020-03-20 21:56:05 +00:00
"bytes"
2019-07-08 23:48:49 +00:00
"context"
2020-01-17 06:58:10 +00:00
"fmt"
2020-07-01 18:26:15 +00:00
big2 "math/big"
"sort"
2019-08-20 16:50:17 +00:00
"sync"
2019-07-08 23:48:49 +00:00
"time"
2020-07-15 14:50:32 +00:00
"github.com/filecoin-project/go-address"
2020-02-12 22:12:11 +00:00
"github.com/filecoin-project/specs-actors/actors/abi"
2020-07-01 18:26:15 +00:00
"github.com/filecoin-project/specs-actors/actors/abi/big"
2020-05-18 21:51:42 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin/power"
2020-04-08 19:06:41 +00:00
"github.com/filecoin-project/specs-actors/actors/crypto"
2020-02-12 22:12:11 +00:00
lru "github.com/hashicorp/golang-lru"
2019-11-25 04:45:13 +00:00
"github.com/filecoin-project/lotus/api"
2019-10-18 04:47:41 +00:00
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/gen"
2020-04-29 22:25:48 +00:00
"github.com/filecoin-project/lotus/chain/store"
2019-10-18 04:47:41 +00:00
"github.com/filecoin-project/lotus/chain/types"
2020-05-14 19:28:33 +00:00
"github.com/filecoin-project/lotus/chain/vm"
2019-08-20 16:50:17 +00:00
2020-01-08 19:10:57 +00:00
logging "github.com/ipfs/go-log/v2"
2019-08-20 16:50:17 +00:00
"go.opencensus.io/trace"
"golang.org/x/xerrors"
2019-07-08 23:48:49 +00:00
)
var log = logging . Logger ( "miner" )
2020-04-23 21:12:42 +00:00
// returns a callback reporting whether we mined a blocks in this round
2020-07-03 18:52:40 +00:00
type waitFunc func ( ctx context . Context , baseTime uint64 ) ( func ( bool , error ) , error )
2019-09-23 15:27:30 +00:00
2020-05-27 18:24:26 +00:00
func NewMiner ( api api . FullNode , epp gen . WinningPoStProver , addr address . Address ) * Miner {
2020-01-17 07:10:47 +00:00
arc , err := lru . NewARC ( 10000 )
if err != nil {
panic ( err )
}
2019-07-11 02:36:43 +00:00
return & Miner {
2020-05-05 19:01:44 +00:00
api : api ,
epp : epp ,
address : addr ,
2020-07-03 18:52:40 +00:00
waitFunc : func ( ctx context . Context , baseTime uint64 ) ( func ( bool , error ) , error ) {
2019-10-09 04:38:59 +00:00
// Wait around for half the block time in case other parents come in
2020-06-30 14:01:30 +00:00
deadline := baseTime + build . PropagationDelaySecs
2020-07-10 14:43:14 +00:00
build . Clock . Sleep ( build . Clock . Until ( time . Unix ( int64 ( deadline ) , 0 ) ) )
2019-12-03 20:00:04 +00:00
2020-07-03 18:52:40 +00:00
return func ( bool , error ) { } , nil
2019-10-09 04:38:59 +00:00
} ,
2020-01-17 06:58:10 +00:00
minedBlockHeights : arc ,
2019-07-11 02:36:43 +00:00
}
2019-07-08 23:48:49 +00:00
}
type Miner struct {
2019-11-25 04:45:13 +00:00
api api . FullNode
2019-07-08 23:48:49 +00:00
2020-05-27 18:24:26 +00:00
epp gen . WinningPoStProver
2019-11-21 22:21:45 +00:00
2020-05-05 19:01:44 +00:00
lk sync . Mutex
address address . Address
stop chan struct { }
stopping chan struct { }
2019-07-11 02:36:43 +00:00
2019-10-09 04:38:59 +00:00
waitFunc waitFunc
2019-07-08 23:48:49 +00:00
lastWork * MiningBase
2020-01-17 06:58:10 +00:00
minedBlockHeights * lru . ARCCache
2019-07-08 23:48:49 +00:00
}
2020-05-05 19:01:44 +00:00
func ( m * Miner ) Address ( ) address . Address {
2019-08-21 15:14:38 +00:00
m . lk . Lock ( )
defer m . lk . Unlock ( )
2020-05-05 19:01:44 +00:00
return m . address
2019-08-21 15:14:38 +00:00
}
2020-05-05 19:01:44 +00:00
func ( m * Miner ) Start ( ctx context . Context ) error {
2019-08-20 16:50:17 +00:00
m . lk . Lock ( )
defer m . lk . Unlock ( )
2020-05-05 19:01:44 +00:00
if m . stop != nil {
return fmt . Errorf ( "miner already started" )
2019-08-20 16:50:17 +00:00
}
2020-05-05 19:01:44 +00:00
m . stop = make ( chan struct { } )
go m . mine ( context . TODO ( ) )
2019-08-20 16:50:17 +00:00
return nil
}
2020-05-05 19:01:44 +00:00
func ( m * Miner ) Stop ( ctx context . Context ) error {
2019-08-20 18:05:17 +00:00
m . lk . Lock ( )
2020-05-05 19:01:44 +00:00
m . stopping = make ( chan struct { } )
stopping := m . stopping
close ( m . stop )
2019-08-20 18:05:17 +00:00
2020-07-20 08:15:01 +00:00
m . lk . Unlock ( )
2020-05-05 19:01:44 +00:00
select {
case <- stopping :
return nil
case <- ctx . Done ( ) :
return ctx . Err ( )
2019-08-20 18:05:17 +00:00
}
}
2020-04-27 22:54:41 +00:00
func ( m * Miner ) niceSleep ( d time . Duration ) bool {
select {
2020-07-10 14:43:14 +00:00
case <- build . Clock . After ( d ) :
2020-04-27 22:54:41 +00:00
return true
case <- m . stop :
return false
}
}
2019-08-20 16:50:17 +00:00
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-08-20 18:05:17 +00:00
2019-10-10 02:03:42 +00:00
var lastBase MiningBase
2019-10-10 00:38:39 +00:00
2019-07-08 23:48:49 +00:00
for {
2019-08-20 18:05:17 +00:00
select {
case <- m . stop :
2019-11-19 23:21:54 +00:00
stopping := m . stopping
2019-08-20 18:05:17 +00:00
m . stop = nil
m . stopping = nil
2019-11-19 23:21:54 +00:00
close ( stopping )
2019-08-20 18:17:59 +00:00
return
2019-11-19 23:21:54 +00:00
2019-08-20 18:05:17 +00:00
default :
}
2019-10-09 09:18:33 +00:00
2019-12-03 20:00:04 +00:00
prebase , err := m . GetBestMiningCandidate ( ctx )
if err != nil {
log . Errorf ( "failed to get best mining candidate: %s" , err )
2020-04-27 22:54:41 +00:00
m . niceSleep ( time . Second * 5 )
2019-12-03 20:00:04 +00:00
continue
}
// Wait until propagation delay period after block we plan to mine on
2020-04-23 21:12:42 +00:00
onDone , err := m . waitFunc ( ctx , prebase . TipSet . MinTimestamp ( ) )
if err != nil {
2019-10-09 04:38:59 +00:00
log . Error ( err )
2020-06-28 10:20:56 +00:00
continue
2019-10-09 04:38:59 +00:00
}
2019-08-20 18:05:17 +00:00
2019-10-15 05:00:30 +00:00
base , err := m . GetBestMiningCandidate ( ctx )
2019-07-08 23:48:49 +00:00
if err != nil {
log . Errorf ( "failed to get best mining candidate: %s" , err )
continue
}
2020-04-23 21:12:42 +00:00
if base . TipSet . Equals ( lastBase . TipSet ) && lastBase . NullRounds == base . NullRounds {
log . Warnf ( "BestMiningCandidate from the previous round: %s (nulls:%d)" , lastBase . TipSet . Cids ( ) , lastBase . NullRounds )
2020-06-30 13:22:48 +00:00
m . niceSleep ( time . Duration ( build . BlockDelaySecs ) * time . Second )
2019-10-10 00:38:39 +00:00
continue
}
2019-07-08 23:48:49 +00:00
2020-05-05 19:01:44 +00:00
b , err := m . mineOne ( ctx , base )
if err != nil {
log . Errorf ( "mining block failed: %+v" , err )
2020-06-08 16:51:47 +00:00
m . niceSleep ( time . Second )
2020-07-03 18:52:40 +00:00
onDone ( false , err )
2020-05-05 19:01:44 +00:00
continue
2019-07-08 23:48:49 +00:00
}
2020-06-08 16:51:47 +00:00
lastBase = * base
2019-07-08 23:48:49 +00:00
2020-07-03 18:52:40 +00:00
onDone ( b != nil , nil )
2020-04-23 21:12:42 +00:00
2020-05-05 19:01:44 +00:00
if b != nil {
btime := time . Unix ( int64 ( b . Header . Timestamp ) , 0 )
2020-07-14 16:12:00 +00:00
now := build . Clock . Now ( )
switch {
case btime == now :
// block timestamp is perfectly aligned with time.
case btime . After ( now ) :
2020-07-10 14:43:14 +00:00
if ! m . niceSleep ( build . Clock . Until ( btime ) ) {
2020-04-27 22:54:41 +00:00
log . Warnf ( "received interrupt while waiting to broadcast block, will shutdown after block is sent out" )
2020-07-10 14:43:14 +00:00
build . Clock . Sleep ( build . Clock . Until ( btime ) )
2020-04-27 22:54:41 +00:00
}
2020-07-14 16:12:00 +00:00
default :
log . Warnw ( "mined block in the past" ,
"block-time" , btime , "time" , build . Clock . Now ( ) , "difference" , build . Clock . Since ( btime ) )
2019-10-09 09:11:41 +00:00
}
2020-05-05 19:39:43 +00:00
// TODO: should do better 'anti slash' protection here
blkKey := fmt . Sprintf ( "%d" , b . Header . Height )
2020-05-05 19:01:44 +00:00
if _ , ok := m . minedBlockHeights . Get ( blkKey ) ; ok {
log . Warnw ( "Created a block at the same height as another block we've created" , "height" , b . Header . Height , "miner" , b . Header . Miner , "parents" , b . Header . Parents )
continue
2019-11-19 19:36:03 +00:00
}
2020-01-17 06:58:10 +00:00
2020-05-05 19:01:44 +00:00
m . minedBlockHeights . Add ( blkKey , true )
if err := m . api . SyncSubmitBlock ( ctx , b ) ; err != nil {
log . Errorf ( "failed to submit newly mined block: %s" , err )
2019-07-08 23:48:49 +00:00
}
2019-10-09 09:18:33 +00:00
} else {
2020-06-12 00:11:38 +00:00
base . NullRounds ++
2020-05-29 10:13:58 +00:00
// Wait until the next epoch, plus the propagation delay, so a new tipset
// has enough time to form.
//
// See: https://github.com/filecoin-project/lotus/issues/1845
2020-06-30 14:01:30 +00:00
nextRound := time . Unix ( int64 ( base . TipSet . MinTimestamp ( ) + build . BlockDelaySecs * uint64 ( base . NullRounds ) ) + int64 ( build . PropagationDelaySecs ) , 0 )
2020-01-23 10:10:42 +00:00
select {
2020-07-10 14:43:14 +00:00
case <- build . Clock . After ( build . Clock . Until ( nextRound ) ) :
2020-01-23 10:10:42 +00:00
case <- m . stop :
stopping := m . stopping
m . stop = nil
m . stopping = nil
close ( stopping )
return
}
2019-07-08 23:48:49 +00:00
}
}
}
type MiningBase struct {
2020-04-23 21:12:42 +00:00
TipSet * types . TipSet
NullRounds abi . ChainEpoch
2019-07-08 23:48:49 +00:00
}
2019-10-15 05:00:30 +00:00
func ( m * Miner ) GetBestMiningCandidate ( ctx context . Context ) ( * MiningBase , error ) {
2020-06-24 15:05:24 +00:00
m . lk . Lock ( )
defer m . lk . Unlock ( )
2019-10-15 05:00:30 +00:00
bts , err := m . api . ChainHead ( ctx )
2019-07-08 23:48:49 +00:00
if err != nil {
return nil , err
}
if m . lastWork != nil {
2020-04-23 21:12:42 +00:00
if m . lastWork . TipSet . Equals ( bts ) {
2019-07-08 23:48:49 +00:00
return m . lastWork , nil
}
2020-02-11 23:29:45 +00:00
btsw , err := m . api . ChainTipSetWeight ( ctx , bts . Key ( ) )
2019-10-15 05:00:30 +00:00
if err != nil {
return nil , err
}
2020-04-23 21:12:42 +00:00
ltsw , err := m . api . ChainTipSetWeight ( ctx , m . lastWork . TipSet . Key ( ) )
2019-10-15 05:00:30 +00:00
if err != nil {
return nil , err
}
if types . BigCmp ( btsw , ltsw ) <= 0 {
2019-07-08 23:48:49 +00:00
return m . lastWork , nil
}
}
2020-04-23 21:12:42 +00:00
m . lastWork = & MiningBase { TipSet : bts }
2019-12-03 07:58:38 +00:00
return m . lastWork , nil
2019-07-08 23:48:49 +00:00
}
2019-12-02 19:51:41 +00:00
func ( m * Miner ) hasPower ( ctx context . Context , addr address . Address , ts * types . TipSet ) ( bool , error ) {
2020-05-18 21:51:42 +00:00
mpower , err := m . api . StateMinerPower ( ctx , addr , ts . Key ( ) )
2019-11-29 20:18:34 +00:00
if err != nil {
return false , err
}
2020-05-18 21:51:42 +00:00
return mpower . MinerPower . QualityAdjPower . GreaterThanEqual ( power . ConsensusMinerMinPower ) , nil
2019-11-29 20:18:34 +00:00
}
2020-06-30 13:18:01 +00:00
// mineOne attempts to mine a single block, and does so synchronously, if and
// only if we are eligible to mine.
2020-06-23 21:51:25 +00:00
//
// {hint/landmark}: This method coordinates all the steps involved in mining a
// block, including the condition of whether mine or not at all depending on
// whether we win the round or not.
2020-06-30 13:18:01 +00:00
//
// This method does the following:
//
// 1.
2020-05-05 19:01:44 +00:00
func ( m * Miner ) mineOne ( ctx context . Context , base * MiningBase ) ( * types . BlockMsg , error ) {
2020-04-23 21:12:42 +00:00
log . Debugw ( "attempting to mine a block" , "tipset" , types . LogCids ( base . TipSet . Cids ( ) ) )
2020-07-10 14:43:14 +00:00
start := build . Clock . Now ( )
2019-11-27 14:18:51 +00:00
2020-04-23 21:12:42 +00:00
round := base . TipSet . Height ( ) + base . NullRounds + 1
2020-04-17 23:36:54 +00:00
2020-05-05 19:01:44 +00:00
mbi , err := m . api . MinerGetBaseInfo ( ctx , m . address , round , base . TipSet . Key ( ) )
2020-04-08 15:11:42 +00:00
if err != nil {
2020-04-09 17:13:09 +00:00
return nil , xerrors . Errorf ( "failed to get mining base info: %w" , err )
2020-04-08 15:11:42 +00:00
}
2020-04-23 21:12:42 +00:00
if mbi == nil {
return nil , nil
}
2020-04-08 15:11:42 +00:00
2020-07-10 14:43:14 +00:00
tMBI := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2020-04-09 17:13:09 +00:00
beaconPrev := mbi . PrevBeaconEntry
2020-07-10 14:43:14 +00:00
tDrand := build . Clock . Now ( )
2020-05-27 18:24:26 +00:00
bvals := mbi . BeaconEntries
2020-05-15 09:17:13 +00:00
2020-05-05 19:01:44 +00:00
hasPower , err := m . hasPower ( ctx , m . address , base . TipSet )
2019-11-29 20:18:34 +00:00
if err != nil {
return nil , xerrors . Errorf ( "checking if miner is slashed: %w" , err )
}
2019-12-05 05:49:11 +00:00
if ! hasPower {
2019-12-02 19:51:41 +00:00
// slashed or just have no power yet
2019-11-29 20:18:34 +00:00
return nil , nil
}
2020-07-10 14:43:14 +00:00
tPowercheck := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2020-07-10 14:43:14 +00:00
log . Infof ( "Time delta between now and our mining base: %ds (nulls: %d)" , uint64 ( build . Clock . Now ( ) . Unix ( ) ) - base . TipSet . MinTimestamp ( ) , base . NullRounds )
2019-12-09 16:08:34 +00:00
2020-04-09 17:13:09 +00:00
rbase := beaconPrev
2020-04-08 15:11:42 +00:00
if len ( bvals ) > 0 {
rbase = bvals [ len ( bvals ) - 1 ]
}
2020-05-05 19:01:44 +00:00
ticket , err := m . computeTicket ( ctx , & rbase , base , len ( bvals ) > 0 )
2020-04-08 16:31:16 +00:00
if err != nil {
return nil , xerrors . Errorf ( "scratching ticket failed: %w" , err )
}
2020-05-05 19:01:44 +00:00
winner , err := gen . IsRoundWinner ( ctx , base . TipSet , round , m . address , rbase , mbi , m . api )
2019-07-08 23:48:49 +00:00
if err != nil {
2019-11-25 04:45:13 +00:00
return nil , xerrors . Errorf ( "failed to check if we win next round: %w" , err )
2019-07-08 23:48:49 +00:00
}
2020-04-08 19:06:41 +00:00
if winner == nil {
2019-07-08 23:48:49 +00:00
return nil , nil
}
2020-07-10 14:43:14 +00:00
tTicket := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2020-04-30 20:21:46 +00:00
buf := new ( bytes . Buffer )
2020-05-05 19:01:44 +00:00
if err := m . address . MarshalCBOR ( buf ) ; err != nil {
2020-04-30 20:21:46 +00:00
return nil , xerrors . Errorf ( "failed to marshal miner address: %w" , err )
}
2020-05-01 19:42:59 +00:00
rand , err := store . DrawRandomness ( rbase . Data , crypto . DomainSeparationTag_WinningPoStChallengeSeed , base . TipSet . Height ( ) + base . NullRounds + 1 , buf . Bytes ( ) )
2020-04-17 05:39:55 +00:00
if err != nil {
return nil , xerrors . Errorf ( "failed to get randomness for winning post: %w" , err )
}
prand := abi . PoStRandomness ( rand )
2020-07-10 14:43:14 +00:00
tSeed := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2020-04-17 23:36:54 +00:00
postProof , err := m . epp . ComputeProof ( ctx , mbi . Sectors , prand )
2020-04-17 05:39:55 +00:00
if err != nil {
return nil , xerrors . Errorf ( "failed to compute winning post proof: %w" , err )
}
2019-12-03 18:25:56 +00:00
// get pending messages early,
2020-04-23 21:12:42 +00:00
pending , err := m . api . MpoolPending ( context . TODO ( ) , base . TipSet . Key ( ) )
2019-12-03 18:25:56 +00:00
if err != nil {
return nil , xerrors . Errorf ( "failed to get pending messages: %w" , err )
}
2020-07-10 14:43:14 +00:00
tPending := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2020-04-08 15:11:42 +00:00
// TODO: winning post proof
2020-05-05 19:01:44 +00:00
b , err := m . createBlock ( base , m . address , ticket , winner , bvals , postProof , pending )
2019-07-08 23:48:49 +00:00
if err != nil {
2019-11-22 16:20:56 +00:00
return nil , xerrors . Errorf ( "failed to create block: %w" , err )
2019-07-08 23:48:49 +00:00
}
2020-07-10 14:43:14 +00:00
tCreateBlock := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
dur := tCreateBlock . Sub ( start )
2019-12-03 20:00:04 +00:00
log . Infow ( "mined new block" , "cid" , b . Cid ( ) , "height" , b . Header . Height , "took" , dur )
2020-06-30 13:22:48 +00:00
if dur > time . Second * time . Duration ( build . BlockDelaySecs ) {
2019-12-03 00:08:08 +00:00
log . Warn ( "CAUTION: block production took longer than the block delay. Your computer may not be fast enough to keep up" )
2020-05-15 09:17:13 +00:00
log . Warnw ( "tMinerBaseInfo " , "duration" , tMBI . Sub ( start ) )
log . Warnw ( "tDrand " , "duration" , tDrand . Sub ( tMBI ) )
log . Warnw ( "tPowercheck " , "duration" , tPowercheck . Sub ( tDrand ) )
log . Warnw ( "tTicket " , "duration" , tTicket . Sub ( tPowercheck ) )
log . Warnw ( "tSeed " , "duration" , tSeed . Sub ( tTicket ) )
log . Warnw ( "tPending " , "duration" , tPending . Sub ( tSeed ) )
log . Warnw ( "tCreateBlock " , "duration" , tCreateBlock . Sub ( tPending ) )
2019-12-03 00:08:08 +00:00
}
2019-11-27 14:18:51 +00:00
2019-07-08 23:48:49 +00:00
return b , nil
}
2020-05-05 19:01:44 +00:00
func ( m * Miner ) computeTicket ( ctx context . Context , brand * types . BeaconEntry , base * MiningBase , haveNewEntries bool ) ( * types . Ticket , error ) {
mi , err := m . api . StateMinerInfo ( ctx , m . address , types . EmptyTSK )
2019-08-15 02:30:21 +00:00
if err != nil {
return nil , err
}
2020-04-16 20:38:42 +00:00
worker , err := m . api . StateAccountKey ( ctx , mi . Worker , types . EmptyTSK )
if err != nil {
return nil , err
}
2019-08-15 02:30:21 +00:00
2020-03-20 21:56:05 +00:00
buf := new ( bytes . Buffer )
2020-05-05 19:01:44 +00:00
if err := m . address . MarshalCBOR ( buf ) ; err != nil {
2020-03-20 21:56:05 +00:00
return nil , xerrors . Errorf ( "failed to marshal address to cbor: %w" , err )
}
2020-04-29 22:25:48 +00:00
if ! haveNewEntries {
buf . Write ( base . TipSet . MinTicket ( ) . VRFProof )
}
input , err := store . DrawRandomness ( brand . Data , crypto . DomainSeparationTag_TicketProduction , base . TipSet . Height ( ) + base . NullRounds + 1 - build . TicketRandomnessLookback , buf . Bytes ( ) )
2020-02-23 20:00:47 +00:00
if err != nil {
return nil , err
}
2019-11-19 15:53:00 +00:00
2020-04-16 20:38:42 +00:00
vrfOut , err := gen . ComputeVRF ( ctx , m . api . WalletSign , worker , input )
2019-08-15 02:30:21 +00:00
if err != nil {
return nil , err
}
return & types . Ticket {
2019-10-09 04:38:59 +00:00
VRFProof : vrfOut ,
2019-08-15 02:30:21 +00:00
} , nil
2019-07-08 23:48:49 +00:00
}
2020-04-06 12:47:14 +00:00
func ( m * Miner ) createBlock ( base * MiningBase , addr address . Address , ticket * types . Ticket ,
2020-04-17 05:39:55 +00:00
eproof * types . ElectionProof , bvals [ ] types . BeaconEntry , wpostProof [ ] abi . PoStProof , pending [ ] * types . SignedMessage ) ( * types . BlockMsg , error ) {
2020-04-23 21:12:42 +00:00
msgs , err := SelectMessages ( context . TODO ( ) , m . api . StateGetActor , base . TipSet , pending )
2019-09-26 03:48:53 +00:00
if err != nil {
return nil , xerrors . Errorf ( "message filtering failed: %w" , err )
}
2019-07-11 02:36:43 +00:00
2020-01-07 20:41:26 +00:00
if len ( msgs ) > build . BlockMessageLimit {
2020-01-17 03:36:54 +00:00
log . Error ( "SelectMessages returned too many messages: " , len ( msgs ) )
2020-01-07 20:41:26 +00:00
msgs = msgs [ : build . BlockMessageLimit ]
}
2020-06-30 13:22:48 +00:00
uts := base . TipSet . MinTimestamp ( ) + build . BlockDelaySecs * ( uint64 ( base . NullRounds ) + 1 )
2019-11-19 15:53:00 +00:00
2020-04-23 21:12:42 +00:00
nheight := base . TipSet . Height ( ) + base . NullRounds + 1
2019-09-06 17:44:09 +00:00
2019-07-08 23:48:49 +00:00
// why even return this? that api call could just submit it for us
2020-04-09 00:24:10 +00:00
return m . api . MinerCreateBlock ( context . TODO ( ) , & api . BlockTemplate {
2020-04-17 05:39:55 +00:00
Miner : addr ,
2020-04-23 21:12:42 +00:00
Parents : base . TipSet . Key ( ) ,
2020-04-17 05:39:55 +00:00
Ticket : ticket ,
Eproof : eproof ,
BeaconValues : bvals ,
Messages : msgs ,
Epoch : nheight ,
Timestamp : uts ,
WinningPoStProof : wpostProof ,
2020-04-09 00:24:10 +00:00
} )
2019-07-08 23:48:49 +00:00
}
2020-05-15 09:17:13 +00:00
type actCacheEntry struct {
act * types . Actor
err error
}
type cachedActorLookup struct {
tsk types . TipSetKey
cache map [ address . Address ] actCacheEntry
fallback ActorLookup
}
func ( c * cachedActorLookup ) StateGetActor ( ctx context . Context , a address . Address , tsk types . TipSetKey ) ( * types . Actor , error ) {
if c . tsk == tsk {
e , has := c . cache [ a ]
if has {
return e . act , e . err
}
}
e , err := c . fallback ( ctx , a , tsk )
if c . tsk == tsk {
c . cache [ a ] = actCacheEntry {
act : e , err : err ,
}
}
return e , err
}
2020-02-11 23:29:45 +00:00
type ActorLookup func ( context . Context , address . Address , types . TipSetKey ) ( * types . Actor , error )
2019-09-26 20:47:34 +00:00
2019-12-03 18:25:56 +00:00
func countFrom ( msgs [ ] * types . SignedMessage , from address . Address ) ( out int ) {
for _ , msg := range msgs {
if msg . Message . From == from {
out ++
}
}
return out
}
2020-01-17 03:36:54 +00:00
func SelectMessages ( ctx context . Context , al ActorLookup , ts * types . TipSet , msgs [ ] * types . SignedMessage ) ( [ ] * types . SignedMessage , error ) {
2020-05-15 09:17:13 +00:00
al = ( & cachedActorLookup {
tsk : ts . Key ( ) ,
cache : map [ address . Address ] actCacheEntry { } ,
fallback : al ,
} ) . StateGetActor
2020-07-01 18:26:15 +00:00
type senderMeta struct {
lastReward abi . TokenAmount
lastGasLimit int64
gasReward [ ] abi . TokenAmount
gasLimit [ ] int64
msgs [ ] * types . SignedMessage
}
2019-09-26 03:53:52 +00:00
inclNonces := make ( map [ address . Address ] uint64 )
2020-07-01 18:26:15 +00:00
inclBalances := make ( map [ address . Address ] big . Int )
outBySender := make ( map [ address . Address ] * senderMeta )
2019-12-03 18:25:56 +00:00
2020-05-15 09:17:13 +00:00
tooLowFundMsgs := 0
tooHighNonceMsgs := 0
2020-07-10 14:43:14 +00:00
start := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
vmValid := time . Duration ( 0 )
getbal := time . Duration ( 0 )
2020-07-01 18:26:15 +00:00
sort . Slice ( msgs , func ( i , j int ) bool {
return msgs [ i ] . Message . Nonce < msgs [ j ] . Message . Nonce
} )
2019-09-26 03:48:53 +00:00
for _ , msg := range msgs {
2020-07-10 14:43:14 +00:00
vmstart := build . Clock . Now ( )
2020-01-07 20:41:26 +00:00
2020-05-14 19:44:26 +00:00
minGas := vm . PricelistByEpoch ( ts . Height ( ) ) . OnChainMessage ( msg . ChainLength ( ) ) // TODO: really should be doing just msg.ChainLength() but the sync side of this code doesnt seem to have access to that
2020-06-11 19:59:39 +00:00
if err := msg . VMMessage ( ) . ValidForBlockInclusion ( minGas . Total ( ) ) ; err != nil {
2020-05-14 19:28:33 +00:00
log . Warnf ( "invalid message in message pool: %s" , err )
continue
}
2020-07-10 14:43:14 +00:00
vmValid += build . Clock . Since ( vmstart )
2020-05-15 09:17:13 +00:00
2020-05-13 05:36:43 +00:00
// TODO: this should be in some more general 'validate message' call
if msg . Message . GasLimit > build . BlockGasLimit {
log . Warnf ( "message in mempool had too high of a gas limit (%d)" , msg . Message . GasLimit )
continue
}
2019-10-14 03:28:19 +00:00
if msg . Message . To == address . Undef {
log . Warnf ( "message in mempool had bad 'To' address" )
continue
}
2019-09-26 03:53:52 +00:00
from := msg . Message . From
2019-09-26 03:48:53 +00:00
2020-07-10 14:43:14 +00:00
getBalStart := build . Clock . Now ( )
2019-09-26 03:53:52 +00:00
if _ , ok := inclNonces [ from ] ; ! ok {
2020-02-11 23:29:45 +00:00
act , err := al ( ctx , from , ts . Key ( ) )
2019-12-03 06:41:28 +00:00
if err != nil {
2020-01-14 15:12:26 +00:00
log . Warnf ( "failed to check message sender balance, skipping message: %+v" , err )
continue
2019-12-03 06:41:28 +00:00
}
2019-12-03 20:05:54 +00:00
inclNonces [ from ] = act . Nonce
inclBalances [ from ] = act . Balance
2019-09-26 03:53:52 +00:00
}
2020-07-10 14:43:14 +00:00
getbal += build . Clock . Since ( getBalStart )
2019-09-26 03:53:52 +00:00
2019-09-26 20:47:34 +00:00
if inclBalances [ from ] . LessThan ( msg . Message . RequiredFunds ( ) ) {
2020-05-15 09:17:13 +00:00
tooLowFundMsgs ++
// todo: drop from mpool
2019-09-26 03:48:53 +00:00
continue
}
2019-09-26 03:53:52 +00:00
if msg . Message . Nonce > inclNonces [ from ] {
2020-05-15 09:17:13 +00:00
tooHighNonceMsgs ++
2019-09-26 20:47:34 +00:00
continue
}
if msg . Message . Nonce < inclNonces [ from ] {
2019-12-03 18:25:56 +00:00
log . Warnf ( "message in mempool has already used nonce (%d < %d), from %s, to %s, %s (%d pending for)" , msg . Message . Nonce , inclNonces [ from ] , msg . Message . From , msg . Message . To , msg . Cid ( ) , countFrom ( msgs , from ) )
2019-09-26 03:53:52 +00:00
continue
}
2019-09-26 20:47:34 +00:00
inclNonces [ from ] = msg . Message . Nonce + 1
inclBalances [ from ] = types . BigSub ( inclBalances [ from ] , msg . Message . RequiredFunds ( ) )
2020-07-01 18:26:15 +00:00
sm := outBySender [ from ]
if sm == nil {
sm = & senderMeta {
lastReward : big . Zero ( ) ,
}
}
sm . gasLimit = append ( sm . gasLimit , sm . lastGasLimit + msg . Message . GasLimit )
sm . lastGasLimit = sm . gasLimit [ len ( sm . gasLimit ) - 1 ]
estimatedReward := big . Mul ( types . NewInt ( uint64 ( msg . Message . GasLimit ) ) , msg . Message . GasPrice )
// TODO: estimatedReward = estimatedReward * (guessActualGasUse(msg) / msg.GasLimit)
sm . gasReward = append ( sm . gasReward , big . Add ( sm . lastReward , estimatedReward ) )
sm . lastReward = sm . gasReward [ len ( sm . gasReward ) - 1 ]
sm . msgs = append ( sm . msgs , msg )
outBySender [ from ] = sm
}
gasLimitLeft := int64 ( build . BlockGasLimit )
2020-07-15 14:50:32 +00:00
orderedSenders := make ( [ ] address . Address , 0 , len ( outBySender ) )
for k := range outBySender {
orderedSenders = append ( orderedSenders , k )
}
sort . Slice ( orderedSenders , func ( i , j int ) bool {
return bytes . Compare ( orderedSenders [ i ] . Bytes ( ) , orderedSenders [ j ] . Bytes ( ) ) == - 1
} )
2020-07-01 18:26:15 +00:00
out := make ( [ ] * types . SignedMessage , 0 , build . BlockMessageLimit )
{
for {
var bestSender address . Address
var nBest int
var bestGasToReward float64
// TODO: This is O(n^2)-ish, could use something like container/heap to cache this math
2020-07-15 14:50:32 +00:00
for _ , sender := range orderedSenders {
meta , ok := outBySender [ sender ]
if ! ok {
continue
}
2020-07-01 18:26:15 +00:00
for n := range meta . msgs {
if meta . gasLimit [ n ] > gasLimitLeft {
break
}
if n + len ( out ) > build . BlockMessageLimit {
break
}
gasToReward , _ := new ( big2 . Float ) . SetInt ( meta . gasReward [ n ] . Int ) . Float64 ( )
gasToReward /= float64 ( meta . gasLimit [ n ] )
if gasToReward >= bestGasToReward {
bestSender = sender
nBest = n + 1
bestGasToReward = gasToReward
}
}
}
if nBest == 0 {
break // block gas limit reached
}
{
out = append ( out , outBySender [ bestSender ] . msgs [ : nBest ] ... )
gasLimitLeft -= outBySender [ bestSender ] . gasLimit [ nBest - 1 ]
2019-09-26 03:53:52 +00:00
2020-07-01 18:26:15 +00:00
outBySender [ bestSender ] . msgs = outBySender [ bestSender ] . msgs [ nBest : ]
outBySender [ bestSender ] . gasLimit = outBySender [ bestSender ] . gasLimit [ nBest : ]
outBySender [ bestSender ] . gasReward = outBySender [ bestSender ] . gasReward [ nBest : ]
if len ( outBySender [ bestSender ] . msgs ) == 0 {
delete ( outBySender , bestSender )
}
}
if len ( out ) >= build . BlockMessageLimit {
break
}
2019-12-04 06:18:02 +00:00
}
2019-09-26 03:48:53 +00:00
}
2020-05-15 09:17:13 +00:00
if tooLowFundMsgs > 0 {
log . Warnf ( "%d messages in mempool does not have enough funds" , tooLowFundMsgs )
}
if tooHighNonceMsgs > 0 {
2020-07-01 10:53:27 +00:00
log . Warnf ( "%d messages in mempool had too high nonce" , tooHighNonceMsgs )
2020-05-15 09:17:13 +00:00
}
2020-07-10 14:43:14 +00:00
sm := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
if sm . Sub ( start ) > time . Second {
log . Warnw ( "SelectMessages took a long time" ,
"duration" , sm . Sub ( start ) ,
"vmvalidate" , vmValid ,
"getbalance" , getbal ,
"msgs" , len ( msgs ) )
}
2019-09-26 03:48:53 +00:00
return out , nil
2019-07-08 23:48:49 +00:00
}