lotus/cmd/lotus-sim/simulation/precommit.go

234 lines
6.4 KiB
Go

package simulation
import (
"context"
"fmt"
"time"
"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/big"
"github.com/filecoin-project/go-state-types/network"
"github.com/ipfs/go-cid"
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"
tutils "github.com/filecoin-project/specs-actors/v5/support/testing"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/actors/policy"
"github.com/filecoin-project/lotus/chain/types"
)
var (
targetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL"))
minFunds = abi.TokenAmount(types.MustParseFIL("100FIL"))
)
// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner.
func makeCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid {
return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix)
}
// packPreCommits packs pre-commit messages until the block is full.
func (ss *simulationState) packPreCommits(ctx context.Context, cb packFunc) (_err error) {
var (
full bool
top1Count, top10Count, restCount int
)
start := time.Now()
defer func() {
if _err != nil {
return
}
log.Debugw("packed pre commits",
"done", top1Count+top10Count+restCount,
"top1", top1Count,
"top10", top10Count,
"rest", restCount,
"filled-block", full,
"duration", time.Since(start),
)
}()
var top1Miners, top10Miners, restMiners int
for i := 0; ; i++ {
var (
minerAddr address.Address
count *int
)
// We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each.
// This won't yeild the most accurate distribution... but it'll give us a good
// enough distribution.
// NOTE: We submit at most _one_ 819 sector batch per-miner per-block. See the
// comment on packPreCommitsMiner for why. We should fix this.
switch {
case (i%3) <= 0 && top1Miners < ss.minerDist.top1.len():
count = &top1Count
minerAddr = ss.minerDist.top1.next()
top1Miners++
case (i%3) <= 1 && top10Miners < ss.minerDist.top10.len():
count = &top10Count
minerAddr = ss.minerDist.top10.next()
top10Miners++
case (i%3) <= 2 && restMiners < ss.minerDist.rest.len():
count = &restCount
minerAddr = ss.minerDist.rest.next()
restMiners++
default:
// Well, we've run through all miners.
return nil
}
var (
added int
err error
)
added, full, err = ss.packPreCommitsMiner(ctx, cb, minerAddr, maxProveCommitBatchSize)
if err != nil {
return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err)
}
*count += added
if full {
return nil
}
}
}
// packPreCommitsMiner packs count pre-commits for the given miner. This should only be called once
// per-miner, per-epoch to avoid packing multiple pre-commits with the same sector numbers.
func (ss *simulationState) packPreCommitsMiner(ctx context.Context, cb packFunc, minerAddr address.Address, count int) (int, bool, error) {
// Load everything.
epoch := ss.nextEpoch()
nv := ss.StateManager.GetNtwkVersion(ctx, epoch)
actor, minerState, err := ss.getMinerState(ctx, minerAddr)
if err != nil {
return 0, false, err
}
minerInfo, err := ss.getMinerInfo(ctx, minerAddr)
if err != nil {
return 0, false, err
}
// Make sure the miner is funded.
minerBalance, err := minerState.AvailableBalance(actor.Balance)
if err != nil {
return 0, false, err
}
if big.Cmp(minerBalance, minFunds) < 0 {
err := fund(cb, minerAddr, 1)
if err != nil {
if err == ErrOutOfGas {
return 0, true, nil
}
return 0, false, err
}
}
// Generate pre-commits.
sealType, err := miner.PreferredSealProofTypeFromWindowPoStType(
nv, minerInfo.WindowPoStProofType,
)
if err != nil {
return 0, false, err
}
sectorNos, err := minerState.UnallocatedSectorNumbers(count)
if err != nil {
return 0, false, err
}
expiration := epoch + policy.GetMaxSectorExpirationExtension()
infos := make([]miner.SectorPreCommitInfo, len(sectorNos))
for i, sno := range sectorNos {
infos[i] = miner.SectorPreCommitInfo{
SealProof: sealType,
SectorNumber: sno,
SealedCID: makeCommR(minerAddr, sno),
SealRandEpoch: epoch - 1,
Expiration: expiration,
}
}
// Commit the pre-commits.
added := 0
if nv >= network.Version13 {
targetBatchSize := maxPreCommitBatchSize
for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize {
batch := infos
if len(batch) > targetBatchSize {
batch = batch[:targetBatchSize]
}
params := miner5.PreCommitSectorBatchParams{
Sectors: batch,
}
enc, err := actors.SerializeParams(&params)
if err != nil {
return added, false, err
}
// NOTE: just in-case, sendAndFund will "fund" and re-try for any message
// that fails due to "insufficient funds".
if _, err := sendAndFund(cb, &types.Message{
To: minerAddr,
From: minerInfo.Worker,
Value: abi.NewTokenAmount(0),
Method: miner.Methods.PreCommitSectorBatch,
Params: enc,
}); err == ErrOutOfGas {
// try again with a smaller batch.
targetBatchSize /= 2
continue
} else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() {
// Log the error and move on. No reason to stop.
log.Errorw("failed to pre-commit for unknown reasons",
"error", aerr,
"miner", minerAddr,
"sectors", batch,
"epoch", ss.nextEpoch(),
)
return added, false, nil
} else if err != nil {
return added, false, err
}
for _, info := range batch {
if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil {
return added, false, err
}
added++
}
infos = infos[len(batch):]
}
}
for _, info := range infos {
enc, err := actors.SerializeParams(&info)
if err != nil {
return 0, false, err
}
if _, err := sendAndFund(cb, &types.Message{
To: minerAddr,
From: minerInfo.Worker,
Value: abi.NewTokenAmount(0),
Method: miner.Methods.PreCommitSector,
Params: enc,
}); err == ErrOutOfGas {
return added, true, nil
} else if err != nil {
return added, false, err
}
if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil {
return added, false, err
}
added++
}
return added, false, nil
}