lotus/extern/storage-sealing/states_sealing.go

459 lines
16 KiB
Go
Raw Normal View History

package sealing
import (
"bytes"
"context"
2020-02-27 00:42:39 +00:00
"golang.org/x/xerrors"
2020-04-06 20:23:37 +00:00
"github.com/filecoin-project/go-statemachine"
2020-02-08 02:18:32 +00:00
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
2020-02-12 07:44:20 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin"
2020-02-11 03:31:28 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/specs-storage/storage"
)
2020-06-25 15:46:06 +00:00
var DealSectorPriority = 1024
func (m *Sealing) handlePacking(ctx statemachine.Context, sector SectorInfo) error {
2020-04-06 22:31:33 +00:00
log.Infow("performing filling up rest of the sector...", "sector", sector.SectorNumber)
2020-02-08 02:18:32 +00:00
var allocated abi.UnpaddedPieceSize
for _, piece := range sector.Pieces {
allocated += piece.Piece.Size.Unpadded()
}
2020-03-03 22:19:22 +00:00
ubytes := abi.PaddedPieceSize(m.sealer.SectorSize()).Unpadded()
if allocated > ubytes {
return xerrors.Errorf("too much data in sector: %d > %d", allocated, ubytes)
}
fillerSizes, err := fillersFromRem(ubytes - allocated)
if err != nil {
return err
}
if len(fillerSizes) > 0 {
2020-04-06 22:31:33 +00:00
log.Warnf("Creating %d filler pieces for sector %d", len(fillerSizes), sector.SectorNumber)
}
2020-07-23 11:55:06 +00:00
fillerPieces, err := m.pledgeSector(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), sector.existingPieceSizes(), fillerSizes...)
if err != nil {
return xerrors.Errorf("filling up the sector (%v): %w", fillerSizes, err)
}
return ctx.Send(SectorPacked{FillerPieces: fillerPieces})
}
2020-06-02 21:45:28 +00:00
func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.SealRandomness, abi.ChainEpoch, error) {
tok, epoch, err := m.api.ChainHead(ctx.Context())
if err != nil {
log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err)
2020-06-02 21:45:28 +00:00
return nil, 0, nil
}
ticketEpoch := epoch - SealRandomnessLookback
2020-06-02 21:45:28 +00:00
buf := new(bytes.Buffer)
if err := m.maddr.MarshalCBOR(buf); err != nil {
return nil, 0, err
}
pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok)
if err != nil {
return nil, 0, xerrors.Errorf("getting precommit info: %w", err)
}
if pci != nil {
ticketEpoch = pci.Info.SealRandEpoch
}
2020-08-11 23:58:35 +00:00
rand, err := m.api.ChainGetRandomnessFromTickets(ctx.Context(), tok, crypto.DomainSeparationTag_SealRandomness, ticketEpoch, buf.Bytes())
2020-06-02 21:45:28 +00:00
if err != nil {
return nil, 0, err
}
2020-06-02 21:45:28 +00:00
return abi.SealRandomness(rand), ticketEpoch, nil
}
func (m *Sealing) handlePreCommit1(ctx statemachine.Context, sector SectorInfo) error {
if err := checkPieces(ctx.Context(), m.maddr, sector, m.api); err != nil { // Sanity check state
switch err.(type) {
2020-01-23 16:02:55 +00:00
case *ErrApi:
2020-04-03 16:54:01 +00:00
log.Errorf("handlePreCommit1: api error, not proceeding: %+v", err)
return nil
2020-01-23 16:02:55 +00:00
case *ErrInvalidDeals:
log.Warnf("invalid deals in sector %d: %v", sector.SectorNumber, err)
2020-08-27 21:14:46 +00:00
return ctx.Send(SectorInvalidDealIDs{Return: RetPreCommit1})
2020-01-23 16:02:55 +00:00
case *ErrExpiredDeals: // Probably not much we can do here, maybe re-pack the sector?
return ctx.Send(SectorDealsExpired{xerrors.Errorf("expired dealIDs in sector: %w", err)})
default:
return xerrors.Errorf("checkPieces sanity check error: %w", err)
}
}
2020-04-06 22:31:33 +00:00
log.Infow("performing sector replication...", "sector", sector.SectorNumber)
2020-06-02 21:45:28 +00:00
ticketValue, ticketEpoch, err := m.getTicket(ctx, sector)
if err != nil {
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("getting ticket failed: %w", err)})
}
pc1o, err := m.sealer.SealPreCommit1(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), ticketValue, sector.pieceInfos())
2020-03-03 22:19:22 +00:00
if err != nil {
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("seal pre commit(1) failed: %w", err)})
2020-03-03 22:19:22 +00:00
}
2020-04-03 16:54:01 +00:00
return ctx.Send(SectorPreCommit1{
PreCommit1Out: pc1o,
TicketValue: ticketValue,
TicketEpoch: ticketEpoch,
2020-04-03 16:54:01 +00:00
})
}
func (m *Sealing) handlePreCommit2(ctx statemachine.Context, sector SectorInfo) error {
cids, err := m.sealer.SealPreCommit2(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), sector.PreCommit1Out)
if err != nil {
return ctx.Send(SectorSealPreCommit2Failed{xerrors.Errorf("seal pre commit(2) failed: %w", err)})
}
2020-04-03 16:54:01 +00:00
return ctx.Send(SectorPreCommit2{
2020-03-22 20:44:27 +00:00
Unsealed: cids.Unsealed,
Sealed: cids.Sealed,
})
}
// TODO: We should probably invoke this method in most (if not all) state transition failures after handlePreCommitting
func (m *Sealing) remarkForUpgrade(sid abi.SectorNumber) {
err := m.MarkForUpgrade(sid)
if err != nil {
log.Errorf("error re-marking sector %d as for upgrade: %+v", sid, err)
}
}
func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInfo) error {
tok, height, err := m.api.ChainHead(ctx.Context())
if err != nil {
log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err)
return nil
}
waddr, err := m.api.StateMinerWorkerAddress(ctx.Context(), m.maddr, tok)
if err != nil {
log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err)
return nil
}
if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.api); err != nil {
2020-06-02 21:45:28 +00:00
switch err := err.(type) {
2020-01-23 16:02:55 +00:00
case *ErrApi:
log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err)
return nil
2020-04-03 16:54:01 +00:00
case *ErrBadCommD: // TODO: Should this just back to packing? (not really needed since handlePreCommit1 will do that too)
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad CommD error: %w", err)})
2020-01-23 16:02:55 +00:00
case *ErrExpiredTicket:
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("ticket expired: %w", err)})
2020-06-02 21:45:28 +00:00
case *ErrBadTicket:
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad ticket: %w", err)})
case *ErrInvalidDeals:
log.Warnf("invalid deals in sector %d: %v", sector.SectorNumber, err)
2020-08-27 21:14:46 +00:00
return ctx.Send(SectorInvalidDealIDs{Return: RetPreCommitting})
case *ErrExpiredDeals:
return ctx.Send(SectorDealsExpired{xerrors.Errorf("sector deals expired: %w", err)})
2020-06-02 21:45:28 +00:00
case *ErrPrecommitOnChain:
return ctx.Send(SectorPreCommitLanded{TipSet: tok}) // we re-did precommit
case *ErrSectorNumberAllocated:
log.Errorf("handlePreCommitFailed: sector number already allocated, not proceeding: %+v", err)
// TODO: check if the sector is committed (not sure how we'd end up here)
return nil
default:
return xerrors.Errorf("checkPrecommit sanity check error: %w", err)
}
}
expiration, err := m.pcp.Expiration(ctx.Context(), sector.Pieces...)
if err != nil {
return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("handlePreCommitting: failed to compute pre-commit expiry: %w", err)})
}
// Sectors must last _at least_ MinSectorExpiration + MaxProveCommitDuration.
2020-08-12 04:14:37 +00:00
// TODO: The "+10" allows the pre-commit to take 10 blocks to be accepted.
if minExpiration := height + miner.MaxProveCommitDuration[sector.SectorType] + miner.MinSectorExpiration + 10; expiration < minExpiration {
2020-08-12 04:14:37 +00:00
expiration = minExpiration
}
// TODO: enforce a reasonable _maximum_ sector lifetime?
2020-02-14 00:24:24 +00:00
params := &miner.SectorPreCommitInfo{
2020-06-15 13:13:35 +00:00
Expiration: expiration,
SectorNumber: sector.SectorNumber,
SealProof: sector.SectorType,
2020-02-12 00:58:55 +00:00
2020-02-27 00:42:39 +00:00
SealedCID: *sector.CommR,
SealRandEpoch: sector.TicketEpoch,
DealIDs: sector.dealIDs(),
2020-02-14 21:38:30 +00:00
}
2020-07-15 14:51:02 +00:00
depositMinimum := m.tryUpgradeSector(ctx.Context(), params)
2020-07-01 13:30:25 +00:00
enc := new(bytes.Buffer)
if err := params.MarshalCBOR(enc); err != nil {
return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("could not serialize pre-commit sector parameters: %w", err)})
}
collateral, err := m.api.StateMinerPreCommitDepositForPower(ctx.Context(), m.maddr, *params, tok)
2020-06-26 15:58:29 +00:00
if err != nil {
return xerrors.Errorf("getting initial pledge collateral: %w", err)
}
2020-07-01 14:33:59 +00:00
deposit := big.Max(depositMinimum, collateral)
2020-08-05 01:30:58 +00:00
log.Infof("submitting precommit for sector %d (deposit: %s): ", sector.SectorNumber, deposit)
2020-08-12 17:47:00 +00:00
mcid, err := m.api.SendMsg(ctx.Context(), waddr, m.maddr, builtin.MethodsMiner.PreCommitSector, deposit, m.feeCfg.MaxPreCommitGasFee, enc.Bytes())
if err != nil {
if params.ReplaceCapacity {
m.remarkForUpgrade(params.ReplaceSectorNumber)
}
2020-04-03 16:54:01 +00:00
return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("pushing message to mpool: %w", err)})
}
2020-07-01 14:34:05 +00:00
return ctx.Send(SectorPreCommitted{Message: mcid, PreCommitDeposit: deposit, PreCommitInfo: *params})
}
2020-05-18 22:49:21 +00:00
func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInfo) error {
if sector.PreCommitMessage == nil {
return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("precommit message was nil")})
}
// would be ideal to just use the events.Called handler, but it wouldn't be able to handle individual message timeouts
2020-04-06 22:31:33 +00:00
log.Info("Sector precommitted: ", sector.SectorNumber)
mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.PreCommitMessage)
if err != nil {
2020-04-03 16:54:01 +00:00
return ctx.Send(SectorChainPreCommitFailed{err})
}
switch mw.Receipt.ExitCode {
case exitcode.Ok:
// this is what we expect
case exitcode.SysErrOutOfGas:
// gas estimator guessed a wrong number
return ctx.Send(SectorRetryPreCommit{})
default:
log.Error("sector precommit failed: ", mw.Receipt.ExitCode)
err := xerrors.Errorf("sector precommit failed: %d", mw.Receipt.ExitCode)
2020-04-03 16:54:01 +00:00
return ctx.Send(SectorChainPreCommitFailed{err})
}
2020-04-06 22:31:33 +00:00
log.Info("precommit message landed on chain: ", sector.SectorNumber)
2020-05-18 22:49:21 +00:00
return ctx.Send(SectorPreCommitLanded{TipSet: mw.TipSetTok})
}
func (m *Sealing) handleWaitSeed(ctx statemachine.Context, sector SectorInfo) error {
2020-08-05 01:30:58 +00:00
tok, _, err := m.api.ChainHead(ctx.Context())
if err != nil {
log.Errorf("handleCommitting: api error, not proceeding: %+v", err)
return nil
}
pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok)
if err != nil {
return xerrors.Errorf("getting precommit info: %w", err)
}
if pci == nil {
return ctx.Send(SectorChainPreCommitFailed{error: xerrors.Errorf("precommit info not found on chain")})
}
randHeight := pci.PreCommitEpoch + miner.PreCommitChallengeDelay
2020-08-05 01:30:58 +00:00
err = m.events.ChainAt(func(ectx context.Context, _ TipSetToken, curH abi.ChainEpoch) error {
// in case of null blocks the randomness can land after the tipset we
// get from the events API
tok, _, err := m.api.ChainHead(ctx.Context())
if err != nil {
log.Errorf("handleCommitting: api error, not proceeding: %+v", err)
return nil
}
2020-04-20 18:21:11 +00:00
buf := new(bytes.Buffer)
if err := m.maddr.MarshalCBOR(buf); err != nil {
return err
}
2020-08-11 23:58:35 +00:00
rand, err := m.api.ChainGetRandomnessFromBeacon(ectx, tok, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, randHeight, buf.Bytes())
if err != nil {
2020-06-17 15:19:36 +00:00
err = xerrors.Errorf("failed to get randomness for computing seal proof (ch %d; rh %d; tsk %x): %w", curH, randHeight, tok, err)
2020-06-17 15:19:36 +00:00
_ = ctx.Send(SectorChainPreCommitFailed{error: err})
return err
}
2020-04-06 20:23:37 +00:00
_ = ctx.Send(SectorSeedReady{SeedValue: abi.InteractiveSealRandomness(rand), SeedEpoch: randHeight})
return nil
}, func(ctx context.Context, ts TipSetToken) error {
log.Warn("revert in interactive commit sector step")
// TODO: need to cancel running process and restart...
return nil
2020-04-06 20:23:37 +00:00
}, InteractivePoRepConfidence, randHeight)
if err != nil {
log.Warn("waitForPreCommitMessage ChainAt errored: ", err)
}
return nil
}
func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) error {
if sector.CommitMessage != nil {
log.Warnf("sector %d entered committing state with a commit message cid", sector.SectorNumber)
ml, err := m.api.StateSearchMsg(ctx.Context(), *sector.CommitMessage)
if err != nil {
log.Warnf("sector %d searching existing commit message %s: %+v", sector.SectorNumber, *sector.CommitMessage, err)
}
if ml != nil {
// some weird retry paths can lead here
return ctx.Send(SectorRetryCommitWait{})
}
}
log.Info("scheduling seal proof computation...")
2020-04-06 22:31:33 +00:00
log.Infof("KOMIT %d %x(%d); %x(%d); %v; r:%x; d:%x", sector.SectorNumber, sector.TicketValue, sector.TicketEpoch, sector.SeedValue, sector.SeedEpoch, sector.pieceInfos(), sector.CommR, sector.CommD)
2020-02-23 20:32:14 +00:00
if sector.CommD == nil || sector.CommR == nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")})
}
2020-03-17 20:19:52 +00:00
cids := storage.SectorCids{
Unsealed: *sector.CommD,
Sealed: *sector.CommR,
}
c2in, err := m.sealer.SealCommit1(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), sector.TicketValue, sector.SeedValue, sector.pieceInfos(), cids)
2020-03-03 22:19:22 +00:00
if err != nil {
return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(1): %w", err)})
2020-03-03 22:19:22 +00:00
}
proof, err := m.sealer.SealCommit2(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), c2in)
if err != nil {
return ctx.Send(SectorComputeProofFailed{xerrors.Errorf("computing seal proof failed(2): %w", err)})
}
return ctx.Send(SectorCommitted{
Proof: proof,
})
}
func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo) error {
tok, _, err := m.api.ChainHead(ctx.Context())
if err != nil {
log.Errorf("handleCommitting: api error, not proceeding: %+v", err)
return nil
}
if err := m.checkCommit(ctx.Context(), sector, sector.Proof, tok); err != nil {
2020-04-04 01:50:05 +00:00
return ctx.Send(SectorCommitFailed{xerrors.Errorf("commit check error: %w", err)})
}
2020-02-12 07:44:20 +00:00
params := &miner.ProveCommitSectorParams{
2020-04-06 22:31:33 +00:00
SectorNumber: sector.SectorNumber,
Proof: sector.Proof,
2020-02-12 07:44:20 +00:00
}
enc := new(bytes.Buffer)
if err := params.MarshalCBOR(enc); err != nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("could not serialize commit sector parameters: %w", err)})
}
waddr, err := m.api.StateMinerWorkerAddress(ctx.Context(), m.maddr, tok)
if err != nil {
log.Errorf("handleCommitting: api error, not proceeding: %+v", err)
return nil
}
2020-08-05 01:30:58 +00:00
pci, err := m.api.StateSectorPreCommitInfo(ctx.Context(), m.maddr, sector.SectorNumber, tok)
2020-06-26 15:58:29 +00:00
if err != nil {
return xerrors.Errorf("getting precommit info: %w", err)
}
if pci == nil {
return ctx.Send(SectorCommitFailed{error: xerrors.Errorf("precommit info not found on chain")})
}
collateral, err := m.api.StateMinerInitialPledgeCollateral(ctx.Context(), m.maddr, pci.Info, tok)
if err != nil {
return xerrors.Errorf("getting initial pledge collateral: %w", err)
}
2020-06-26 15:58:29 +00:00
collateral = big.Sub(collateral, pci.PreCommitDeposit)
if collateral.LessThan(big.Zero()) {
collateral = big.Zero()
}
// TODO: check seed / ticket / deals are up to date
2020-08-12 17:47:00 +00:00
mcid, err := m.api.SendMsg(ctx.Context(), waddr, m.maddr, builtin.MethodsMiner.ProveCommitSector, collateral, m.feeCfg.MaxCommitGasFee, enc.Bytes())
if err != nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("pushing message to mpool: %w", err)})
}
return ctx.Send(SectorCommitSubmitted{
Message: mcid,
})
}
func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) error {
if sector.CommitMessage == nil {
2020-04-06 22:31:33 +00:00
log.Errorf("sector %d entered commit wait state without a message cid", sector.SectorNumber)
return ctx.Send(SectorCommitFailed{xerrors.Errorf("entered commit wait with no commit cid")})
}
mw, err := m.api.StateWaitMsg(ctx.Context(), *sector.CommitMessage)
if err != nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("failed to wait for porep inclusion: %w", err)})
}
switch mw.Receipt.ExitCode {
case exitcode.Ok:
// this is what we expect
case exitcode.SysErrOutOfGas:
// gas estimator guessed a wrong number
return ctx.Send(SectorRetrySubmitCommit{})
default:
return ctx.Send(SectorCommitFailed{xerrors.Errorf("submitting sector proof failed (exit=%d, msg=%s) (t:%x; s:%x(%d); p:%x)", mw.Receipt.ExitCode, sector.CommitMessage, sector.TicketValue, sector.SeedValue, sector.SeedEpoch, sector.Proof)})
}
si, err := m.api.StateSectorGetInfo(ctx.Context(), m.maddr, sector.SectorNumber, mw.TipSetTok)
2020-05-28 00:10:50 +00:00
if err != nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("proof validation failed, calling StateSectorGetInfo: %w", err)})
}
if si == nil {
return ctx.Send(SectorCommitFailed{xerrors.Errorf("proof validation failed, sector not found in sector set after cron")})
2020-05-28 00:10:50 +00:00
}
return ctx.Send(SectorProving{})
}
2020-01-29 21:25:06 +00:00
func (m *Sealing) handleFinalizeSector(ctx statemachine.Context, sector SectorInfo) error {
2020-01-29 22:37:31 +00:00
// TODO: Maybe wait for some finality
if err := m.sealer.FinalizeSector(sector.sealingCtx(ctx.Context()), m.minerSector(sector.SectorNumber), sector.keepUnsealedRanges(false)); err != nil {
2020-03-03 22:19:22 +00:00
return ctx.Send(SectorFinalizeFailed{xerrors.Errorf("finalize sector: %w", err)})
2020-01-29 21:25:06 +00:00
}
return ctx.Send(SectorFinalized{})
}
2020-07-02 20:09:59 +00:00
func (m *Sealing) handleProvingSector(ctx statemachine.Context, sector SectorInfo) error {
// TODO: track sector health / expiration
log.Infof("Proving sector %d", sector.SectorNumber)
if err := m.sealer.ReleaseUnsealed(ctx.Context(), m.minerSector(sector.SectorNumber), sector.keepUnsealedRanges(true)); err != nil {
2020-07-02 20:09:59 +00:00
log.Error(err)
}
// TODO: Watch termination
// TODO: Auto-extend if set
return nil
}