lotus/miner/miner.go

698 lines
22 KiB
Go
Raw Normal View History

package miner
import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
"fmt"
2021-06-29 02:00:21 +00:00
"os"
2019-08-20 16:50:17 +00:00
"sync"
"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
"github.com/filecoin-project/go-address"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"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"
"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"
"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"
"github.com/filecoin-project/lotus/chain/types"
cliutil "github.com/filecoin-project/lotus/cli/util"
"github.com/filecoin-project/lotus/journal"
)
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.
type waitFunc func(ctx context.Context, baseTime uint64) (func(bool, abi.ChainEpoch, error), abi.ChainEpoch, error)
2019-09-23 15:27:30 +00:00
func randTimeOffset(width time.Duration) time.Duration {
buf := make([]byte, 8)
rand.Reader.Read(buf) //nolint:errcheck
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{
api: api,
epp: epp,
address: addr,
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
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
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,
minedBlockHeights: arc,
2020-08-11 12:48:32 +00:00
evtTypes: [...]journal.EventType{
evtTypeBlockMined: j.RegisterEventType("miner", "block_mined"),
2020-08-11 12:48:32 +00:00
},
journal: j,
2019-07-11 02:36:43 +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.
type Miner struct {
2021-04-05 17:56:53 +00:00
api v1api.FullNode
epp gen.WinningPoStProver
2019-11-21 22:21:45 +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
2020-06-30 10:26:27 +00:00
// lastWork holds the last MiningBase we built upon.
lastWork *MiningBase
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
journal journal.Journal
}
2020-06-30 10:26:27 +00:00
// Address returns the address of the miner.
func (m *Miner) Address() address.Address {
2019-08-21 15:14:38 +00:00
m.lk.Lock()
defer m.lk.Unlock()
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()
if m.stop != nil {
return fmt.Errorf("miner already started")
2019-08-20 16:50:17 +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.
func (m *Miner) Stop(ctx context.Context) error {
2019-08-20 18:05:17 +00:00
m.lk.Lock()
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()
select {
case <-stopping:
return nil
case <-ctx.Done():
return ctx.Err()
2019-08-20 18:05:17 +00:00
}
}
func (m *Miner) niceSleep(d time.Duration) bool {
select {
2020-07-10 14:43:14 +00:00
case <-build.Clock.After(d):
return true
case <-m.stop:
2020-09-16 20:56:04 +00:00
log.Infow("received interrupt while trying to sleep in mining cycle")
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) {
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:
for {
ctx := cliutil.OnSingleNode(ctx)
2019-08-20 18:05:17 +00:00
select {
case <-m.stop:
stopping := m.stopping
2019-08-20 18:05:17 +00:00
m.stop = nil
m.stopping = nil
close(stopping)
return
2019-08-20 18:05:17 +00:00
default:
}
2020-08-07 16:09:53 +00:00
var base *MiningBase
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)
if !m.niceSleep(time.Second * 5) {
2020-09-24 14:03:24 +00:00
continue minerLoop
}
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 {
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
_, 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)
if !m.niceSleep(time.Second) {
2020-09-24 14:03:24 +00:00
continue minerLoop
}
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)
if !m.niceSleep(time.Duration(build.BlockDelaySecs) * time.Second) {
2020-09-24 14:03:24 +00:00
continue minerLoop
}
continue
}
b, err := m.mineOne(ctx, base)
if err != nil {
log.Errorf("mining block failed: %+v", err)
if !m.niceSleep(time.Second) {
2020-09-24 14:03:24 +00:00
continue minerLoop
}
onDone(false, 0, err)
continue
}
lastBase = *base
var h abi.ChainEpoch
if b != nil {
h = b.Header.Height
}
onDone(b != nil, h, nil)
2020-04-23 21:12:42 +00:00
if b != nil {
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(),
}
})
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)) {
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-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
}
if os.Getenv("LOTUS_MINER_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" {
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 {
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
}
if _, ok := m.minedBlockHeights.Get(b.Header.Height); 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
}
m.minedBlockHeights.Add(b.Header.Height, true)
2020-08-06 01:14:13 +00:00
if err := m.api.SyncSubmitBlock(ctx, b); err != nil {
log.Errorf("failed to submit newly mined block: %+v", err)
}
} else {
2020-06-12 00:11:38 +00:00
base.NullRounds++
// 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)
select {
2020-07-10 14:43:14 +00:00
case <-build.Clock.After(build.Clock.Until(nextRound)):
case <-m.stop:
stopping := m.stopping
m.stop = nil
m.stopping = nil
close(stopping)
return
}
}
}
}
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.
type MiningBase struct {
2023-08-10 20:07:08 +00:00
TipSet *types.TipSet
ComputeTime time.Time
NullRounds abi.ChainEpoch
}
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) {
m.lk.Lock()
defer m.lk.Unlock()
2019-10-15 05:00:30 +00:00
bts, err := m.api.ChainHead(ctx)
if err != nil {
return nil, err
}
if m.lastWork != nil {
2020-04-23 21:12:42 +00:00
if m.lastWork.TipSet.Equals(bts) {
return m.lastWork, nil
}
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 {
m.lastWork = nil
2019-10-15 05:00:30 +00:00
return nil, err
}
if types.BigCmp(btsw, ltsw) <= 0 {
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
}
// 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.
//
// This method does the following:
//
2022-08-29 14:25:30 +00:00
// 1.
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()))
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
// always write out a log
var winner *types.ElectionProof
var mbi *api.MiningBaseInfo
var rbase types.BeaconEntry
defer func() {
var hasMinPower bool
// 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,
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
}
}
isLate := uint64(tStart.Unix()) > (base.TipSet.MinTimestamp() + uint64(base.NullRounds*builtin.EpochDurationSeconds) + build.PropagationDelaySecs)
logStruct := []interface{}{
"tookMilliseconds", (build.Clock.Now().UnixNano() - tStart.UnixNano()) / 1_000_000,
"forRound", int64(round),
"baseEpoch", int64(base.TipSet.Height()),
"baseDeltaSeconds", uint64(tStart.Unix()) - base.TipSet.MinTimestamp(),
"nullRounds", int64(base.NullRounds),
"lateStart", isLate,
2021-05-24 14:04:37 +00:00
"beaconEpoch", rbase.Round,
"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
"networkPowerAtLookback", mbi.NetworkPower.String(),
"minerPowerAtLookback", mbi.MinerPower.String(),
"isEligible", mbi.EligibleForMining,
"isWinner", (winner != nil),
"error", err,
}
if err != nil {
log.Errorw("completed mineOne", logStruct...)
} else if isLate || (hasMinPower && !mbi.EligibleForMining) {
log.Warnw("completed mineOne", logStruct...)
} else {
log.Infow("completed mineOne", logStruct...)
}
}()
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
}
if !mbi.EligibleForMining {
// 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
bvals := mbi.BeaconEntries
rbase = mbi.PrevBeaconEntry
2020-04-08 15:11:42 +00:00
if len(bvals) > 0 {
rbase = bvals[len(bvals)-1]
}
ticket, err := m.computeTicket(ctx, &rbase, round, base.TipSet.MinTicket(), mbi)
if err != nil {
err = xerrors.Errorf("scratching ticket failed: %w", err)
return nil, err
}
winner, err = gen.IsRoundWinner(ctx, round, m.address, rbase, mbi, m.api)
if err != nil {
err = xerrors.Errorf("failed to check if we win next round: %w", err)
return nil, err
}
if winner == nil {
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)
if err := m.address.MarshalCBOR(buf); err != nil {
err = xerrors.Errorf("failed to marshal miner address: %w", err)
return nil, err
2020-04-30 20:21:46 +00:00
}
rand, err := lrand.DrawRandomnessFromBase(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes())
if err != nil {
err = xerrors.Errorf("failed to get randomness for winning post: %w", err)
return nil, err
}
prand := abi.PoStRandomness(rand)
2020-07-10 14:43:14 +00:00
tSeed := build.Clock.Now()
nv, err := m.api.StateNetworkVersion(ctx, base.TipSet.Key())
if err != nil {
return nil, err
}
2020-05-15 09:17:13 +00:00
postProof, err := m.epp.ComputeProof(ctx, mbi.Sectors, prand, round, nv)
if err != nil {
err = xerrors.Errorf("failed to compute winning post proof: %w", err)
return nil, err
}
tProof := build.Clock.Now()
2019-12-03 18:25:56 +00:00
// get pending messages early,
msgs, err := m.api.MpoolSelect(ctx, base.TipSet.Key(), ticket.Quality())
2019-12-03 18:25:56 +00:00
if err != nil {
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()
// 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.
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
}
// If the base has changed, we take the _intersection_ of our old base and new base,
// 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{}{}
}
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 {
refreshedBaseBlocks = append(refreshedBaseBlocks, baseBlk)
2023-08-10 20:07:08 +00:00
}
}
if len(refreshedBaseBlocks) != len(base.TipSet.Blocks()) {
refreshedBase, err := types.NewTipSet(refreshedBaseBlocks)
if err != nil {
err = xerrors.Errorf("failed to create new tipset when refreshing: %w", err)
return nil, err
}
if !base.TipSet.MinTicket().Equals(refreshedBase.MinTicket()) {
2023-08-28 16:10:48 +00:00
log.Warn("recomputing ticket due to base refresh")
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-28 16:10:48 +00:00
log.Warn("re-selecting messages due to base refresh")
// refresh messages, as the selected messages may no longer be valid
msgs, err = m.api.MpoolSelect(ctx, refreshedBase.Key(), ticket.Quality())
if err != nil {
err = xerrors.Errorf("failed to re-select messages for block: %w", err)
return nil, err
}
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
minedBlock, err = m.createBlock(base, m.address, ticket, winner, bvals, postProof, msgs)
if err != nil {
err = xerrors.Errorf("failed to create block: %w", err)
return nil, err
}
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
}
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)
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",
"tPowercheck ", tPowercheck.Sub(tStart),
2020-07-29 21:20:07 +00:00
"tTicket ", tTicket.Sub(tPowercheck),
"tSeed ", tSeed.Sub(tTicket),
"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
return minedBlock, nil
}
func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry, round abi.ChainEpoch, chainRand *types.Ticket, mbi *api.MiningBaseInfo) (*types.Ticket, error) {
buf := new(bytes.Buffer)
if err := m.address.MarshalCBOR(buf); err != nil {
return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err)
}
2020-04-29 22:25:48 +00:00
if round > build.UpgradeSmokeHeight {
buf.Write(chainRand.VRFProof)
2020-04-29 22:25:48 +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
}
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
}
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) {
uts := base.TipSet.MinTimestamp() + build.BlockDelaySecs*(uint64(base.NullRounds)+1)
2020-04-23 21:12:42 +00:00
nheight := base.TipSet.Height() + base.NullRounds + 1
2019-09-06 17:44:09 +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{
Miner: addr,
2020-04-23 21:12:42 +00:00
Parents: base.TipSet.Key(),
Ticket: ticket,
Eproof: eproof,
BeaconValues: bvals,
Messages: msgs,
Epoch: nheight,
Timestamp: uts,
WinningPoStProof: wpostProof,
2020-04-09 00:24:10 +00:00
})
}