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/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) 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(¶ms) if err != nil { return 0, 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 err != nil { return 0, false, err } for _, info := range batch { if err := ss.commitQueue.enqueueProveCommit(minerAddr, epoch, info); err != nil { return 0, 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 0, false, err } added++ } return added, false, nil }