package simulation import ( "context" "github.com/filecoin-project/go-bitfield" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" ) // packProveCOmmits packs all prove-commits for all "ready to be proven" sectors until it fills the // block or runs out. func (ss *simulationState) packProveCommits(ctx context.Context, cb packFunc) (_full bool, _err error) { // Roll the commitQueue forward. ss.commitQueue.advanceEpoch(ss.nextEpoch()) var failed, done, unbatched, count int defer func() { if _err != nil { return } remaining := ss.commitQueue.ready() log.Debugw("packed prove commits", "remaining", remaining, "done", done, "failed", failed, "unbatched", unbatched, "miners-processed", count, "filled-block", _full, ) }() for { addr, pending, ok := ss.commitQueue.nextMiner() if !ok { return false, nil } res, full, err := ss.packProveCommitsMiner(ctx, cb, addr, pending) if err != nil { return false, err } failed += res.failed done += res.done unbatched += res.unbatched count++ if full { return true, nil } } } type proveCommitResult struct { done, failed, unbatched int } // sendAndFund "packs" the given message, funding the actor if necessary. It: // // 1. Tries to send the given message. // 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. // 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from // somewhere) and re-tries the message.0 // // NOTE: If the message fails a second time, the funds won't be "unsent". func sendAndFund(send packFunc, msg *types.Message) (bool, error) { full, err := send(msg) aerr, ok := err.(aerrors.ActorError) if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { return full, err } // Ok, insufficient funds. Let's fund this miner and try again. full, err = send(&types.Message{ From: builtin.BurntFundsActorAddr, To: msg.To, Value: targetFunds, Method: builtin.MethodSend, }) if err != nil { return false, xerrors.Errorf("failed to fund %s: %w", msg.To, err) } // ok, nothing's going to work. if full { return true, nil } return send(msg) } // packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of // available prove-commits, batching as much as possible. // // This function will fund as necessary from the "burnt funds actor" (look, it's convenient). func (ss *simulationState) packProveCommitsMiner( ctx context.Context, cb packFunc, minerAddr address.Address, pending minerPendingCommits, ) (res proveCommitResult, full bool, _err error) { info, err := ss.getMinerInfo(ctx, minerAddr) if err != nil { return res, false, err } nv := ss.sm.GetNtwkVersion(ctx, ss.nextEpoch()) for sealType, snos := range pending { if nv >= network.Version13 { for len(snos) > minProveCommitBatchSize { batchSize := maxProveCommitBatchSize if len(snos) < batchSize { batchSize = len(snos) } batch := snos[:batchSize] snos = snos[batchSize:] proof, err := mockAggregateSealProof(sealType, minerAddr, batchSize) if err != nil { return res, false, err } params := miner5.ProveCommitAggregateParams{ SectorNumbers: bitfield.New(), AggregateProof: proof, } for _, sno := range batch { params.SectorNumbers.Set(uint64(sno)) } enc, err := actors.SerializeParams(¶ms) if err != nil { return res, false, err } if full, err := sendAndFund(cb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), Method: miner.Methods.ProveCommitAggregate, Params: enc, }); err != nil { // If we get a random error, or a fatal actor error, bail. // Otherwise, just log it. if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { return res, false, err } log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, "sectors", batch, "epoch", ss.nextEpoch(), ) res.failed += batchSize } else if full { return res, true, nil } else { res.done += batchSize } pending.finish(sealType, batchSize) } } for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { sno := snos[0] snos = snos[1:] proof, err := mockSealProof(sealType, minerAddr) if err != nil { return res, false, err } params := miner.ProveCommitSectorParams{ SectorNumber: sno, Proof: proof, } enc, err := actors.SerializeParams(¶ms) if err != nil { return res, false, err } if full, err := sendAndFund(cb, &types.Message{ From: info.Worker, To: minerAddr, Value: abi.NewTokenAmount(0), Method: miner.Methods.ProveCommitSector, Params: enc, }); err != nil { if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { return res, false, err } log.Errorw("failed to prove commit sector(s)", "error", err, "miner", minerAddr, "sectors", []abi.SectorNumber{sno}, "epoch", ss.nextEpoch(), ) res.failed++ } else if full { return res, true, nil } else { res.unbatched++ res.done++ } // mark it as "finished" regardless so we skip it. pending.finish(sealType, 1) } // if we get here, we can't pre-commit anything more. } return res, false, nil } // loadProveCommitsMiner enqueue all pending prove-commits for the given miner. This is called on // load to populate the commitQueue and should not need to be called later. // // It will drop any pre-commits that have already expired. func (ss *simulationState) loadProveCommitsMiner(ctx context.Context, addr address.Address, minerState miner.State) error { // Find all pending prove commits and group by proof type. Really, there should never // (except during upgrades be more than one type. nextEpoch := ss.nextEpoch() nv := ss.sm.GetNtwkVersion(ctx, nextEpoch) av := actors.VersionForNetwork(nv) return minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) if nextEpoch > info.PreCommitEpoch+msd { log.Warnw("dropping old pre-commit") return nil } return ss.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) }) }