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-07-21 18:42:38 +00:00
"crypto/rand"
"encoding/binary"
2020-01-17 06:58:10 +00:00
"fmt"
2021-06-29 02:00:21 +00:00
"os"
2019-08-20 16:50:17 +00:00
"sync"
2019-07-08 23:48:49 +00:00
"time"
2023-08-22 15:00:27 +00:00
"github.com/hashicorp/golang-lru/arc/v2"
2023-08-10 20:07:08 +00:00
"github.com/ipfs/go-cid"
2022-06-14 15:00:51 +00:00
logging "github.com/ipfs/go-log/v2"
"go.opencensus.io/trace"
"golang.org/x/xerrors"
2020-08-07 00:05:35 +00:00
2020-07-15 14:50:32 +00:00
"github.com/filecoin-project/go-address"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
2021-05-21 13:00:21 +00:00
"github.com/filecoin-project/go-state-types/big"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/crypto"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/go-state-types/proof"
2020-02-12 22:12:11 +00:00
2019-11-25 04:45:13 +00:00
"github.com/filecoin-project/lotus/api"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/api/v1api"
2019-10-18 04:47:41 +00:00
"github.com/filecoin-project/lotus/build"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/actors/policy"
2019-10-18 04:47:41 +00:00
"github.com/filecoin-project/lotus/chain/gen"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/chain/gen/slashfilter"
lrand "github.com/filecoin-project/lotus/chain/rand"
2019-10-18 04:47:41 +00:00
"github.com/filecoin-project/lotus/chain/types"
2022-10-17 02:52:22 +00:00
cliutil "github.com/filecoin-project/lotus/cli/util"
2020-08-07 20:51:41 +00:00
"github.com/filecoin-project/lotus/journal"
2019-07-08 23:48:49 +00:00
)
var log = logging . Logger ( "miner" )
2020-08-11 12:48:32 +00:00
// Journal event types.
const (
evtTypeBlockMined = iota
)
2020-06-30 10:26:27 +00:00
// waitFunc is expected to pace block mining at the configured network rate.
//
// baseTime is the timestamp of the mining base, i.e. the timestamp
// of the tipset we're planning to construct upon.
//
// Upon each mining loop iteration, the returned callback is called reporting
// whether we mined a block in this round or not.
2020-08-27 00:51:16 +00:00
type waitFunc func ( ctx context . Context , baseTime uint64 ) ( func ( bool , abi . ChainEpoch , error ) , abi . ChainEpoch , error )
2019-09-23 15:27:30 +00:00
2020-07-21 18:42:38 +00:00
func randTimeOffset ( width time . Duration ) time . Duration {
buf := make ( [ ] byte , 8 )
2020-08-20 04:49:10 +00:00
rand . Reader . Read ( buf ) //nolint:errcheck
2020-07-21 18:42:38 +00:00
val := time . Duration ( binary . BigEndian . Uint64 ( buf ) % uint64 ( width ) )
return val - ( width / 2 )
}
2020-06-30 10:26:27 +00:00
// NewMiner instantiates a miner with a concrete WinningPoStProver and a miner
// address (which can be different from the worker's address).
2021-04-05 17:56:53 +00:00
func NewMiner ( api v1api . FullNode , epp gen . WinningPoStProver , addr address . Address , sf * slashfilter . SlashFilter , j journal . Journal ) * Miner {
2023-08-21 03:21:11 +00:00
arc , err := arc . NewARC [ abi . ChainEpoch , bool ] ( 10000 )
2020-01-17 07:10:47 +00:00
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-08-27 00:51:16 +00:00
waitFunc : func ( ctx context . Context , baseTime uint64 ) ( func ( bool , abi . ChainEpoch , error ) , abi . ChainEpoch , error ) {
2020-06-30 10:26:27 +00:00
// wait around for half the block time in case other parents come in
//
// if we're mining a block in the past via catch-up/rush mining,
// such as when recovering from a network halt, this sleep will be
// for a negative duration, and therefore **will return
// immediately**.
//
// the result is that we WILL NOT wait, therefore fast-forwarding
// and thus healing the chain by backfilling it with null rounds
// rapidly.
2020-06-30 14:01:30 +00:00
deadline := baseTime + build . PropagationDelaySecs
2020-07-21 18:42:38 +00:00
baseT := time . Unix ( int64 ( deadline ) , 0 )
baseT = baseT . Add ( randTimeOffset ( time . Second ) )
build . Clock . Sleep ( build . Clock . Until ( baseT ) )
2019-12-03 20:00:04 +00:00
2020-08-27 00:51:16 +00:00
return func ( bool , abi . ChainEpoch , error ) { } , 0 , nil
2019-10-09 04:38:59 +00:00
} ,
2020-08-06 01:14:13 +00:00
2020-08-06 01:16:54 +00:00
sf : sf ,
2020-01-17 06:58:10 +00:00
minedBlockHeights : arc ,
2020-08-11 12:48:32 +00:00
evtTypes : [ ... ] journal . EventType {
2020-10-09 19:52:04 +00:00
evtTypeBlockMined : j . RegisterEventType ( "miner" , "block_mined" ) ,
2020-08-11 12:48:32 +00:00
} ,
2020-10-09 19:52:04 +00:00
journal : j ,
2019-07-11 02:36:43 +00:00
}
2019-07-08 23:48:49 +00:00
}
2020-06-30 10:26:27 +00:00
// Miner encapsulates the mining processes of the system.
//
// Refer to the godocs on mineOne and mine methods for more detail.
2019-07-08 23:48:49 +00:00
type Miner struct {
2021-04-05 17:56:53 +00:00
api v1api . 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
2020-06-30 10:26:27 +00:00
// lastWork holds the last MiningBase we built upon.
2019-07-08 23:48:49 +00:00
lastWork * MiningBase
2020-01-17 06:58:10 +00:00
2020-06-30 10:26:27 +00:00
sf * slashfilter . SlashFilter
// minedBlockHeights is a safeguard that caches the last heights we mined.
// It is consulted before publishing a newly mined block, for a sanity check
// intended to avoid slashings in case of a bug.
2023-08-21 03:21:11 +00:00
minedBlockHeights * arc . ARCCache [ abi . ChainEpoch , bool ]
2020-08-11 12:48:32 +00:00
evtTypes [ 1 ] journal . EventType
2020-10-09 19:52:04 +00:00
journal journal . Journal
2019-07-08 23:48:49 +00:00
}
2020-06-30 10:26:27 +00:00
// Address returns the address of the miner.
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-06-30 10:26:27 +00:00
// Start starts the mining operation. It spawns a goroutine and returns
// immediately. Start is not idempotent.
func ( m * Miner ) Start ( _ 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-06-30 10:26:27 +00:00
// Stop stops the mining operation. It is not idempotent, and multiple adjacent
// calls to Stop will fail.
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 :
2020-09-16 20:56:04 +00:00
log . Infow ( "received interrupt while trying to sleep in mining cycle" )
2020-04-27 22:54:41 +00:00
return false
}
}
2020-06-30 10:26:27 +00:00
// mine runs the mining loop. It performs the following:
//
2022-08-29 14:25:30 +00:00
// 1. Queries our current best currently-known mining candidate (tipset to
// build upon).
// 2. Waits until the propagation delay of the network has elapsed (currently
// 6 seconds). The waiting is done relative to the timestamp of the best
// candidate, which means that if it's way in the past, we won't wait at
// all (e.g. in catch-up or rush mining).
// 3. After the wait, we query our best mining candidate. This will be the one
// we'll work with.
// 4. Sanity check that we _actually_ have a new mining base to mine on. If
// not, wait one epoch + propagation delay, and go back to the top.
// 5. We attempt to mine a block, by calling mineOne (refer to godocs). This
// method will either return a block if we were eligible to mine, or nil
// if we weren't.
// 6a. If we mined a block, we update our state and push it out to the network
// via gossipsub.
// 6b. If we didn't mine a block, we consider this to be a nil round on top of
// the mining base we selected. If other miner or miners on the network
// were eligible to mine, we will receive their blocks via gossipsub and
// we will select that tipset on the next iteration of the loop, thus
// discarding our null round.
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
2020-11-12 14:31:21 +00:00
go m . doWinPoStWarmup ( ctx )
2019-10-10 02:03:42 +00:00
var lastBase MiningBase
2020-09-23 12:24:19 +00:00
minerLoop :
2019-07-08 23:48:49 +00:00
for {
2022-10-05 15:32:24 +00:00
ctx := cliutil . OnSingleNode ( ctx )
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
2020-08-07 16:09:53 +00:00
var base * MiningBase
2020-08-27 00:51:16 +00:00
var onDone func ( bool , abi . ChainEpoch , error )
2020-08-07 16:09:53 +00:00
var injectNulls abi . ChainEpoch
for {
prebase , err := m . GetBestMiningCandidate ( ctx )
if err != nil {
log . Errorf ( "failed to get best mining candidate: %s" , err )
2020-09-16 02:06:40 +00:00
if ! m . niceSleep ( time . Second * 5 ) {
2020-09-24 14:03:24 +00:00
continue minerLoop
2020-09-16 02:06:40 +00:00
}
2020-08-07 16:09:53 +00:00
continue
}
if base != nil && base . TipSet . Height ( ) == prebase . TipSet . Height ( ) && base . NullRounds == prebase . NullRounds {
base = prebase
break
}
if base != nil {
2020-08-27 00:51:16 +00:00
onDone ( false , 0 , nil )
2020-08-07 16:09:53 +00:00
}
// TODO: need to change the orchestration here. the problem is that
// we are waiting *after* we enter this loop and selecta mining
// candidate, which is almost certain to change in multiminer
// tests. Instead, we should block before entering the loop, so
// that when the test 'MineOne' function is triggered, we pull our
// best mining candidate at that time.
// Wait until propagation delay period after block we plan to mine on
onDone , injectNulls , err = m . waitFunc ( ctx , prebase . TipSet . MinTimestamp ( ) )
if err != nil {
log . Error ( err )
continue
}
// just wait for the beacon entry to become available before we select our final mining base
2021-09-27 01:23:41 +00:00
_ , err = m . api . StateGetBeaconEntry ( ctx , prebase . TipSet . Height ( ) + prebase . NullRounds + 1 )
2020-08-07 16:09:53 +00:00
if err != nil {
log . Errorf ( "failed getting beacon entry: %s" , err )
2020-09-16 02:06:40 +00:00
if ! m . niceSleep ( time . Second ) {
2020-09-24 14:03:24 +00:00
continue minerLoop
2020-09-16 02:06:40 +00:00
}
2020-08-07 16:09:53 +00:00
continue
}
base = prebase
2019-12-03 20:00:04 +00:00
}
2020-09-27 07:01:42 +00:00
base . NullRounds += injectNulls // testing
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-09-16 02:06:40 +00:00
if ! m . niceSleep ( time . Duration ( build . BlockDelaySecs ) * time . Second ) {
2020-09-24 14:03:24 +00:00
continue minerLoop
2020-09-16 02:06:40 +00:00
}
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-09-16 02:06:40 +00:00
if ! m . niceSleep ( time . Second ) {
2020-09-24 14:03:24 +00:00
continue minerLoop
2020-09-16 02:06:40 +00:00
}
2020-08-27 00:51:16 +00:00
onDone ( false , 0 , 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-08-27 00:51:16 +00:00
var h abi . ChainEpoch
if b != nil {
h = b . Header . Height
}
onDone ( b != nil , h , nil )
2020-04-23 21:12:42 +00:00
2020-05-05 19:01:44 +00:00
if b != nil {
2020-10-09 19:52:04 +00:00
m . journal . RecordEvent ( m . evtTypes [ evtTypeBlockMined ] , func ( ) interface { } {
2020-08-11 12:48:32 +00:00
return map [ string ] interface { } {
"parents" : base . TipSet . Cids ( ) ,
"nulls" : base . NullRounds ,
"epoch" : b . Header . Height ,
"timestamp" : b . Header . Timestamp ,
"cid" : b . Header . Cid ( ) ,
}
2020-08-07 20:51:41 +00:00
} )
2020-05-05 19:01:44 +00:00
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
}
2023-11-08 12:26:28 +00:00
if os . Getenv ( "LOTUS_MINER_NO_SLASHFILTER" ) != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" && ! build . IsNearUpgrade ( base . TipSet . Height ( ) , build . UpgradeWatermelonFixHeight ) {
2023-06-13 14:35:02 +00:00
witness , fault , err := m . sf . MinedBlock ( ctx , b . Header , base . TipSet . Height ( ) + base . NullRounds )
if err != nil {
log . Errorf ( "<!!> SLASH FILTER ERRORED: %s" , err )
// Continue here, because it's _probably_ wiser to not submit this block
continue
}
if fault {
2023-06-13 14:51:03 +00:00
log . Errorf ( "<!!> SLASH FILTER DETECTED FAULT due to blocks %s and %s" , b . Header . Cid ( ) , witness )
2021-06-29 02:00:21 +00:00
continue
}
2020-08-06 01:14:13 +00:00
}
2023-03-13 22:29:09 +00:00
if _ , ok := m . minedBlockHeights . Get ( b . Header . Height ) ; ok {
2020-05-05 19:01:44 +00:00
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
2023-03-13 22:29:09 +00:00
m . minedBlockHeights . Add ( b . Header . Height , true )
2020-08-06 01:14:13 +00:00
2020-05-05 19:01:44 +00:00
if err := m . api . SyncSubmitBlock ( ctx , b ) ; err != nil {
2020-10-23 17:30:42 +00:00
log . Errorf ( "failed to submit newly mined block: %+v" , 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
}
}
}
2020-06-30 10:26:27 +00:00
// MiningBase is the tipset on top of which we plan to construct our next block.
// Refer to godocs on GetBestMiningCandidate.
2019-07-08 23:48:49 +00:00
type MiningBase struct {
2023-08-10 20:07:08 +00:00
TipSet * types . TipSet
ComputeTime time . Time
NullRounds abi . ChainEpoch
2019-07-08 23:48:49 +00:00
}
2020-06-30 10:26:27 +00:00
// GetBestMiningCandidate implements the fork choice rule from a miner's
// perspective.
//
// It obtains the current chain head (HEAD), and compares it to the last tipset
// we selected as our mining base (LAST). If HEAD's weight is larger than
// LAST's weight, it selects HEAD to build on. Else, it selects LAST.
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 {
2020-09-01 03:44:55 +00:00
m . lastWork = nil
2019-10-15 05:00:30 +00:00
return nil , err
}
if types . BigCmp ( btsw , ltsw ) <= 0 {
2019-07-08 23:48:49 +00:00
return m . lastWork , nil
}
}
2023-08-10 20:07:08 +00:00
m . lastWork = & MiningBase { TipSet : bts , ComputeTime : time . Now ( ) }
2019-12-03 07:58:38 +00:00
return m . lastWork , nil
2019-07-08 23:48:49 +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:
//
2022-08-29 14:25:30 +00:00
// 1.
2021-05-21 13:00:21 +00:00
func ( m * Miner ) mineOne ( ctx context . Context , base * MiningBase ) ( minedBlock * types . BlockMsg , err error ) {
2020-04-23 21:12:42 +00:00
log . Debugw ( "attempting to mine a block" , "tipset" , types . LogCids ( base . TipSet . Cids ( ) ) )
2021-05-31 13:16:38 +00:00
tStart := 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
2021-05-21 13:00:21 +00:00
// always write out a log
2021-05-04 16:18:26 +00:00
var winner * types . ElectionProof
2021-05-21 13:00:21 +00:00
var mbi * api . MiningBaseInfo
var rbase types . BeaconEntry
2021-05-04 16:18:26 +00:00
defer func ( ) {
2021-05-31 12:53:34 +00:00
var hasMinPower bool
2021-05-21 13:00:21 +00:00
// mbi can be nil if we are deep in penalty and there are 0 eligible sectors
// in the current deadline. If this case - put together a dummy one for reporting
// https://github.com/filecoin-project/lotus/blob/v1.9.0/chain/stmgr/utils.go#L500-L502
if mbi == nil {
mbi = & api . MiningBaseInfo {
NetworkPower : big . NewInt ( - 1 ) , // we do not know how big the network is at this point
EligibleForMining : false ,
2021-05-31 12:53:34 +00:00
MinerPower : big . NewInt ( 0 ) , // but we do know we do not have anything eligible
}
// try to opportunistically pull actual power and plug it into the fake mbi
if pow , err := m . api . StateMinerPower ( ctx , m . address , base . TipSet . Key ( ) ) ; err == nil && pow != nil {
hasMinPower = pow . HasMinPower
mbi . MinerPower = pow . MinerPower . QualityAdjPower
mbi . NetworkPower = pow . TotalPower . QualityAdjPower
2021-05-21 13:00:21 +00:00
}
}
2021-05-31 13:16:38 +00:00
isLate := uint64 ( tStart . Unix ( ) ) > ( base . TipSet . MinTimestamp ( ) + uint64 ( base . NullRounds * builtin . EpochDurationSeconds ) + build . PropagationDelaySecs )
2021-05-22 21:55:32 +00:00
logStruct := [ ] interface { } {
2021-05-31 13:16:38 +00:00
"tookMilliseconds" , ( build . Clock . Now ( ) . UnixNano ( ) - tStart . UnixNano ( ) ) / 1_000_000 ,
2021-05-04 16:18:26 +00:00
"forRound" , int64 ( round ) ,
"baseEpoch" , int64 ( base . TipSet . Height ( ) ) ,
2021-05-31 13:16:38 +00:00
"baseDeltaSeconds" , uint64 ( tStart . Unix ( ) ) - base . TipSet . MinTimestamp ( ) ,
2021-05-22 15:39:56 +00:00
"nullRounds" , int64 ( base . NullRounds ) ,
2021-05-22 21:55:32 +00:00
"lateStart" , isLate ,
2021-05-24 14:04:37 +00:00
"beaconEpoch" , rbase . Round ,
2021-05-05 20:54:01 +00:00
"lookbackEpochs" , int64 ( policy . ChainFinality ) , // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186
2021-05-04 16:18:26 +00:00
"networkPowerAtLookback" , mbi . NetworkPower . String ( ) ,
"minerPowerAtLookback" , mbi . MinerPower . String ( ) ,
"isEligible" , mbi . EligibleForMining ,
"isWinner" , ( winner != nil ) ,
2021-05-21 13:00:21 +00:00
"error" , err ,
2021-05-22 21:55:32 +00:00
}
if err != nil {
log . Errorw ( "completed mineOne" , logStruct ... )
2021-05-31 12:53:34 +00:00
} else if isLate || ( hasMinPower && ! mbi . EligibleForMining ) {
2021-05-22 21:55:32 +00:00
log . Warnw ( "completed mineOne" , logStruct ... )
} else {
log . Infow ( "completed mineOne" , logStruct ... )
}
2021-05-04 16:18:26 +00:00
} ( )
2021-05-21 13:00:21 +00:00
mbi , err = m . api . MinerGetBaseInfo ( ctx , m . address , round , base . TipSet . Key ( ) )
if err != nil {
err = xerrors . Errorf ( "failed to get mining base info: %w" , err )
return nil , err
}
if mbi == nil {
return nil , nil
}
2020-10-06 07:49:11 +00:00
if ! mbi . EligibleForMining {
2020-07-27 23:51:30 +00:00
// slashed or just have no power yet
return nil , nil
}
2020-04-08 15:11:42 +00:00
2020-07-10 14:43:14 +00:00
tPowercheck := build . Clock . Now ( )
2020-05-15 09:17:13 +00:00
2021-05-31 13:16:38 +00:00
bvals := mbi . BeaconEntries
rbase = mbi . PrevBeaconEntry
2020-04-08 15:11:42 +00:00
if len ( bvals ) > 0 {
rbase = bvals [ len ( bvals ) - 1 ]
}
2023-08-11 13:52:26 +00:00
ticket , err := m . computeTicket ( ctx , & rbase , round , base . TipSet . MinTicket ( ) , mbi )
2020-04-08 16:31:16 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "scratching ticket failed: %w" , err )
return nil , err
2020-04-08 16:31:16 +00:00
}
2023-08-11 13:48:04 +00:00
winner , err = gen . IsRoundWinner ( ctx , round , m . address , rbase , mbi , m . api )
2019-07-08 23:48:49 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to check if we win next round: %w" , err )
return nil , 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 {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to marshal miner address: %w" , err )
return nil , err
2020-04-30 20:21:46 +00:00
}
2022-12-18 19:59:34 +00:00
rand , err := lrand . DrawRandomnessFromBase ( rbase . Data , crypto . DomainSeparationTag_WinningPoStChallengeSeed , round , buf . Bytes ( ) )
2020-04-17 05:39:55 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to get randomness for winning post: %w" , err )
return nil , err
2020-04-17 05:39:55 +00:00
}
prand := abi . PoStRandomness ( rand )
2020-07-10 14:43:14 +00:00
tSeed := build . Clock . Now ( )
2021-12-08 17:11:19 +00:00
nv , err := m . api . StateNetworkVersion ( ctx , base . TipSet . Key ( ) )
if err != nil {
return nil , err
}
2020-05-15 09:17:13 +00:00
2021-12-08 17:11:19 +00:00
postProof , err := m . epp . ComputeProof ( ctx , mbi . Sectors , prand , round , nv )
2020-04-17 05:39:55 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to compute winning post proof: %w" , err )
return nil , err
2020-04-17 05:39:55 +00:00
}
2020-11-25 10:05:06 +00:00
tProof := build . Clock . Now ( )
2019-12-03 18:25:56 +00:00
// get pending messages early,
2023-08-11 14:31:18 +00:00
msgs , err := m . api . MpoolSelect ( ctx , base . TipSet . Key ( ) , ticket . Quality ( ) )
2019-12-03 18:25:56 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to select messages for block: %w" , err )
return nil , err
2019-12-03 18:25:56 +00:00
}
2023-08-10 20:07:08 +00:00
tEquivocateWait := build . Clock . Now ( )
2023-08-11 14:31:18 +00:00
// This next block exists to "catch" equivocating miners,
// who submit 2 blocks at the same height at different times in order to split the network.
// To safeguard against this, we make sure it's been EquivocationDelaySecs since our base was calculated,
// then re-calculate it.
// If the daemon detected equivocated blocks, those blocks will no longer be in the new base.
2023-08-11 14:36:17 +00:00
m . niceSleep ( time . Until ( base . ComputeTime . Add ( time . Duration ( build . EquivocationDelaySecs ) * time . Second ) ) )
2023-08-10 20:07:08 +00:00
newBase , err := m . GetBestMiningCandidate ( ctx )
if err != nil {
err = xerrors . Errorf ( "failed to refresh best mining candidate: %w" , err )
return nil , err
}
2023-08-11 14:36:17 +00:00
// If the base has changed, we take the _intersection_ of our old base and new base,
2023-08-11 14:31:18 +00:00
// thus ejecting blocks from any equivocating miners, without taking any new blocks.
2023-08-22 15:00:27 +00:00
if newBase . TipSet . Height ( ) == base . TipSet . Height ( ) && ! newBase . TipSet . Equals ( base . TipSet ) {
2023-08-28 16:10:48 +00:00
log . Warnf ( "base changed from %s to %s, taking intersection" , base . TipSet . Key ( ) , newBase . TipSet . Key ( ) )
2023-08-10 20:07:08 +00:00
newBaseMap := map [ cid . Cid ] struct { } { }
for _ , newBaseBlk := range newBase . TipSet . Cids ( ) {
newBaseMap [ newBaseBlk ] = struct { } { }
}
2023-08-11 14:31:18 +00:00
refreshedBaseBlocks := make ( [ ] * types . BlockHeader , 0 , len ( base . TipSet . Cids ( ) ) )
2023-08-10 20:07:08 +00:00
for _ , baseBlk := range base . TipSet . Blocks ( ) {
if _ , ok := newBaseMap [ baseBlk . Cid ( ) ] ; ok {
2023-08-11 14:31:18 +00:00
refreshedBaseBlocks = append ( refreshedBaseBlocks , baseBlk )
2023-08-10 20:07:08 +00:00
}
}
2023-10-24 17:16:48 +00:00
if len ( refreshedBaseBlocks ) != 0 && len ( refreshedBaseBlocks ) != len ( base . TipSet . Blocks ( ) ) {
2023-08-11 14:31:18 +00:00
refreshedBase , err := types . NewTipSet ( refreshedBaseBlocks )
if err != nil {
err = xerrors . Errorf ( "failed to create new tipset when refreshing: %w" , err )
return nil , err
}
2023-08-11 14:36:17 +00:00
if ! base . TipSet . MinTicket ( ) . Equals ( refreshedBase . MinTicket ( ) ) {
2023-08-28 16:10:48 +00:00
log . Warn ( "recomputing ticket due to base refresh" )
2023-08-11 14:36:17 +00:00
ticket , err = m . computeTicket ( ctx , & rbase , round , refreshedBase . MinTicket ( ) , mbi )
if err != nil {
err = xerrors . Errorf ( "failed to refresh ticket: %w" , err )
return nil , err
}
}
2023-08-11 14:31:18 +00:00
2023-08-28 16:10:48 +00:00
log . Warn ( "re-selecting messages due to base refresh" )
2023-08-11 14:31:18 +00:00
// refresh messages, as the selected messages may no longer be valid
2023-08-11 14:36:17 +00:00
msgs , err = m . api . MpoolSelect ( ctx , refreshedBase . Key ( ) , ticket . Quality ( ) )
2023-08-11 14:31:18 +00:00
if err != nil {
err = xerrors . Errorf ( "failed to re-select messages for block: %w" , err )
return nil , err
}
2023-08-11 14:36:17 +00:00
base . TipSet = refreshedBase
2023-08-10 20:07:08 +00:00
}
}
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
2021-05-21 13:00:21 +00:00
minedBlock , err = m . createBlock ( base , m . address , ticket , winner , bvals , postProof , msgs )
2019-07-08 23:48:49 +00:00
if err != nil {
2021-05-21 13:00:21 +00:00
err = xerrors . Errorf ( "failed to create block: %w" , err )
return nil , err
2019-07-08 23:48:49 +00:00
}
2020-07-10 14:43:14 +00:00
tCreateBlock := build . Clock . Now ( )
2021-05-31 13:31:40 +00:00
dur := tCreateBlock . Sub ( tStart )
2020-08-07 15:36:15 +00:00
parentMiners := make ( [ ] address . Address , len ( base . TipSet . Blocks ( ) ) )
for i , header := range base . TipSet . Blocks ( ) {
parentMiners [ i ] = header . Miner
}
2021-05-21 13:00:21 +00:00
log . Infow ( "mined new block" , "cid" , minedBlock . Cid ( ) , "height" , int64 ( minedBlock . Header . Height ) , "miner" , minedBlock . Header . Miner , "parents" , parentMiners , "parentTipset" , base . TipSet . Key ( ) . String ( ) , "took" , dur )
2020-06-30 13:22:48 +00:00
if dur > time . Second * time . Duration ( build . BlockDelaySecs ) {
2020-07-29 21:20:07 +00:00
log . Warnw ( "CAUTION: block production took longer than the block delay. Your computer may not be fast enough to keep up" ,
2021-05-31 13:16:38 +00:00
"tPowercheck " , tPowercheck . Sub ( tStart ) ,
2020-07-29 21:20:07 +00:00
"tTicket " , tTicket . Sub ( tPowercheck ) ,
"tSeed " , tSeed . Sub ( tTicket ) ,
2020-11-25 10:05:06 +00:00
"tProof " , tProof . Sub ( tSeed ) ,
2023-08-10 20:07:08 +00:00
"tEquivocateWait " , tEquivocateWait . Sub ( tProof ) ,
"tPending " , tPending . Sub ( tEquivocateWait ) ,
2020-07-29 21:20:07 +00:00
"tCreateBlock " , tCreateBlock . Sub ( tPending ) )
2019-12-03 00:08:08 +00:00
}
2019-11-27 14:18:51 +00:00
2021-05-21 13:00:21 +00:00
return minedBlock , nil
2019-07-08 23:48:49 +00:00
}
2023-08-11 13:52:26 +00:00
func ( m * Miner ) computeTicket ( ctx context . Context , brand * types . BeaconEntry , round abi . ChainEpoch , chainRand * types . Ticket , mbi * api . MiningBaseInfo ) ( * types . Ticket , error ) {
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
2020-09-10 03:06:23 +00:00
if round > build . UpgradeSmokeHeight {
2023-08-11 13:52:26 +00:00
buf . Write ( chainRand . VRFProof )
2020-04-29 22:25:48 +00:00
}
2022-12-18 19:59:34 +00:00
input , err := lrand . DrawRandomnessFromBase ( brand . Data , crypto . DomainSeparationTag_TicketProduction , round - 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-10-22 22:48:09 +00:00
vrfOut , err := gen . ComputeVRF ( ctx , m . api . WalletSign , mbi . WorkerKey , 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 ,
2022-04-20 21:34:28 +00:00
eproof * types . ElectionProof , bvals [ ] types . BeaconEntry , wpostProof [ ] proof . PoStProof , msgs [ ] * types . SignedMessage ) ( * types . BlockMsg , error ) {
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
}