lotus/storage/pipeline/checks.go

253 lines
8.7 KiB
Go
Raw Normal View History

2020-01-20 22:03:50 +00:00
package sealing
import (
2020-04-20 18:21:11 +00:00
"bytes"
2020-01-20 22:03:50 +00:00
"context"
2022-07-15 17:27:03 +00:00
"time"
2022-01-04 07:27:14 +00:00
2020-01-20 22:03:50 +00:00
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-commp-utils/zerocomm"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/crypto"
2022-06-14 15:00:51 +00:00
prooftypes "github.com/filecoin-project/go-state-types/proof"
"github.com/filecoin-project/lotus/chain/actors/policy"
"github.com/filecoin-project/lotus/chain/types"
2020-01-20 22:03:50 +00:00
)
// TODO: For now we handle this by halting state execution, when we get jsonrpc reconnecting
2022-08-29 14:25:30 +00:00
//
// We should implement some wait-for-api logic
type ErrApi struct{ error }
2020-01-23 12:17:45 +00:00
type ErrNoDeals struct{ error }
type ErrInvalidDeals struct{ error }
2020-02-23 00:47:47 +00:00
type ErrInvalidPiece struct{ error }
type ErrExpiredDeals struct{ error }
2020-01-23 12:17:45 +00:00
type ErrBadCommD struct{ error }
type ErrExpiredTicket struct{ error }
2020-06-02 21:45:28 +00:00
type ErrBadTicket struct{ error }
type ErrPrecommitOnChain struct{ error }
type ErrSectorNumberAllocated struct{ error }
2020-01-23 14:05:44 +00:00
type ErrBadSeed struct{ error }
2020-04-04 01:50:05 +00:00
type ErrInvalidProof struct{ error }
2020-06-17 15:19:36 +00:00
type ErrNoPrecommit struct{ error }
type ErrCommitWaitFailed struct{ error }
type ErrBadRU struct{ error }
type ErrBadPR struct{ error }
2022-08-24 19:27:27 +00:00
func checkPieces(ctx context.Context, maddr address.Address, sn abi.SectorNumber, pieces []Piece, api SealingAPI, mustHaveDeals bool) error {
ts, err := api.ChainHead(ctx)
2020-01-23 12:17:45 +00:00
if err != nil {
return &ErrApi{xerrors.Errorf("getting chain head: %w", err)}
2020-01-23 12:17:45 +00:00
}
dealCount := 0
2022-08-24 19:27:27 +00:00
for i, p := range pieces {
// if no deal is associated with the piece, ensure that we added it as
// filler (i.e. ensure that it has a zero PieceCID)
if p.DealInfo == nil {
exp := zerocomm.ZeroPieceCommitment(p.Piece.Size.Unpadded())
if !p.Piece.PieceCID.Equals(exp) {
2022-08-24 19:27:27 +00:00
return &ErrInvalidPiece{xerrors.Errorf("sector %d piece %d had non-zero PieceCID %+v", sn, i, p.Piece.PieceCID)}
2020-02-23 00:47:47 +00:00
}
continue
}
dealCount++
deal, err := api.StateMarketStorageDeal(ctx, p.DealInfo.DealID, ts.Key())
2020-01-20 22:03:50 +00:00
if err != nil {
2020-08-05 01:30:58 +00:00
return &ErrInvalidDeals{xerrors.Errorf("getting deal %d for piece %d: %w", p.DealInfo.DealID, i, err)}
2020-01-20 22:03:50 +00:00
}
if deal.Proposal.Provider != maddr {
2022-08-24 19:27:27 +00:00
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with wrong provider: %s != %s", i, len(pieces), sn, p.DealInfo.DealID, deal.Proposal.Provider, maddr)}
}
if deal.Proposal.PieceCID != p.Piece.PieceCID {
2022-08-24 19:27:27 +00:00
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with wrong PieceCID: %s != %s", i, len(pieces), sn, p.DealInfo.DealID, p.Piece.PieceCID, deal.Proposal.PieceCID)}
2020-01-20 22:03:50 +00:00
}
if p.Piece.Size != deal.Proposal.PieceSize {
2022-08-24 19:27:27 +00:00
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with different size: %d != %d", i, len(pieces), sn, p.DealInfo.DealID, p.Piece.Size, deal.Proposal.PieceSize)}
2020-01-23 12:17:45 +00:00
}
if ts.Height() >= deal.Proposal.StartEpoch {
2022-08-24 19:27:27 +00:00
return &ErrExpiredDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers expired deal %d - should start at %d, head %d", i, len(pieces), sn, p.DealInfo.DealID, deal.Proposal.StartEpoch, ts.Height())}
2020-01-20 22:03:50 +00:00
}
}
if mustHaveDeals && dealCount <= 0 {
2022-08-24 19:27:27 +00:00
return &ErrNoDeals{xerrors.Errorf("sector %d must have deals, but does not", sn)}
}
2020-01-20 22:03:50 +00:00
return nil
}
// checkPrecommit checks that data commitment generated in the sealing process
2022-08-29 14:25:30 +00:00
//
// matches pieces, and that the seal ticket isn't expired
func checkPrecommit(ctx context.Context, maddr address.Address, si SectorInfo, tsk types.TipSetKey, height abi.ChainEpoch, api SealingAPI) (err error) {
2022-08-24 19:27:27 +00:00
if err := checkPieces(ctx, maddr, si.SectorNumber, si.Pieces, api, false); err != nil {
return err
}
commD, err := api.StateComputeDataCID(ctx, maddr, si.SectorType, si.dealIDs(), tsk)
2020-01-20 22:03:50 +00:00
if err != nil {
return &ErrApi{xerrors.Errorf("calling StateComputeDataCommitment: %w", err)}
2020-02-23 00:47:47 +00:00
}
if si.CommD == nil || !commD.Equals(*si.CommD) {
return &ErrBadCommD{xerrors.Errorf("on chain CommD differs from sector: %s != %s", commD, si.CommD)}
2020-01-23 14:05:44 +00:00
}
pci, err := api.StateSectorPreCommitInfo(ctx, maddr, si.SectorNumber, tsk)
2020-06-02 21:45:28 +00:00
if err != nil {
return &ErrApi{xerrors.Errorf("getting precommit info: %w", err)}
}
if pci != nil {
2021-07-27 06:51:45 +00:00
// committed P2 message
2020-06-02 21:45:28 +00:00
if pci.Info.SealRandEpoch != si.TicketEpoch {
2020-08-05 01:30:58 +00:00
return &ErrBadTicket{xerrors.Errorf("bad ticket epoch: %d != %d", pci.Info.SealRandEpoch, si.TicketEpoch)}
2020-06-02 21:45:28 +00:00
}
2020-08-05 01:30:58 +00:00
return &ErrPrecommitOnChain{xerrors.Errorf("precommit already on chain")}
2020-06-02 21:45:28 +00:00
}
alloc, err := api.StateMinerSectorAllocated(ctx, maddr, si.SectorNumber, tsk)
if err != nil {
return xerrors.Errorf("checking if sector is allocated: %w", err)
}
2022-07-15 17:27:03 +00:00
// TODO This is a very bad hack!! We are only using this while we investigate the compiler issue here.
time.Sleep(time.Nanosecond)
if alloc {
//committed P2 message but commit C2 message too late, pci should be null in this case
return &ErrSectorNumberAllocated{xerrors.Errorf("sector %d is allocated, but PreCommit info wasn't found on chain", si.SectorNumber)}
}
2021-07-27 06:51:45 +00:00
//never commit P2 message before, check ticket expiration
ticketEarliest := height - policy.MaxPreCommitRandomnessLookback
if si.TicketEpoch < ticketEarliest {
return &ErrExpiredTicket{xerrors.Errorf("ticket expired: seal height: %d, head: %d", si.TicketEpoch+policy.SealRandomnessLookback, height)}
}
2020-01-20 22:03:50 +00:00
return nil
}
func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, tsk types.TipSetKey) (err error) {
2020-04-06 20:23:37 +00:00
if si.SeedEpoch == 0 {
return &ErrBadSeed{xerrors.Errorf("seed epoch was not set")}
}
pci, err := m.Api.StateSectorPreCommitInfo(ctx, m.maddr, si.SectorNumber, tsk)
if err != nil {
return xerrors.Errorf("getting precommit info: %w", err)
}
2020-06-17 15:19:36 +00:00
if pci == nil {
alloc, err := m.Api.StateMinerSectorAllocated(ctx, m.maddr, si.SectorNumber, tsk)
if err != nil {
return xerrors.Errorf("checking if sector is allocated: %w", err)
}
2022-07-15 17:27:03 +00:00
// TODO This is a very bad hack!! We are only using this while we investigate the compiler issue here.
time.Sleep(time.Nanosecond)
if alloc {
// not much more we can check here, basically try to wait for commit,
// and hope that this will work
if si.CommitMessage != nil {
return &ErrCommitWaitFailed{err}
}
return xerrors.Errorf("sector %d is allocated, but PreCommit info wasn't found on chain", si.SectorNumber)
}
2020-06-17 15:19:36 +00:00
return &ErrNoPrecommit{xerrors.Errorf("precommit info not found on-chain")}
}
if pci.PreCommitEpoch+policy.GetPreCommitChallengeDelay() != si.SeedEpoch {
return &ErrBadSeed{xerrors.Errorf("seed epoch doesn't match on chain info: %d != %d", pci.PreCommitEpoch+policy.GetPreCommitChallengeDelay(), si.SeedEpoch)}
}
2020-04-20 18:21:11 +00:00
buf := new(bytes.Buffer)
if err := m.maddr.MarshalCBOR(buf); err != nil {
return err
}
seed, err := m.Api.StateGetRandomnessFromBeacon(ctx, crypto.DomainSeparationTag_InteractiveSealChallengeSeed, si.SeedEpoch, buf.Bytes(), tsk)
if err != nil {
return &ErrApi{xerrors.Errorf("failed to get randomness for computing seal proof: %w", err)}
}
2020-04-06 20:23:37 +00:00
if string(seed) != string(si.SeedValue) {
return &ErrBadSeed{xerrors.Errorf("seed has changed")}
}
if *si.CommR != pci.Info.SealedCID {
log.Warn("on-chain sealed CID doesn't match!")
}
2022-04-20 21:34:28 +00:00
ok, err := m.verif.VerifySeal(prooftypes.SealVerifyInfo{
SectorID: m.minerSectorID(si.SectorNumber),
2020-05-22 01:03:34 +00:00
SealedCID: pci.Info.SealedCID,
SealProof: pci.Info.SealProof,
2020-05-22 01:03:34 +00:00
Proof: proof,
2020-04-06 20:23:37 +00:00
Randomness: si.TicketValue,
InteractiveRandomness: si.SeedValue,
2020-04-04 01:50:05 +00:00
UnsealedCID: *si.CommD,
})
if err != nil {
return &ErrInvalidProof{xerrors.Errorf("verify seal: %w", err)}
2020-04-04 01:50:05 +00:00
}
if !ok {
return &ErrInvalidProof{xerrors.New("invalid proof (compute error?)")}
}
2022-08-24 19:27:27 +00:00
if err := checkPieces(ctx, m.maddr, si.SectorNumber, si.Pieces, m.Api, false); err != nil {
return err
}
return nil
2020-01-20 22:03:50 +00:00
}
// check that sector info is good after running a replica update
func checkReplicaUpdate(ctx context.Context, maddr address.Address, si SectorInfo, tsk types.TipSetKey, api SealingAPI) error {
2022-08-24 19:27:27 +00:00
if err := checkPieces(ctx, maddr, si.SectorNumber, si.Pieces, api, true); err != nil {
return err
}
if !si.CCUpdate {
return xerrors.Errorf("replica update on sector not marked for update")
}
commD, err := api.StateComputeDataCID(ctx, maddr, si.SectorType, si.dealIDs(), tsk)
if err != nil {
return &ErrApi{xerrors.Errorf("calling StateComputeDataCommitment: %w", err)}
}
2022-02-17 00:24:28 +00:00
if si.UpdateUnsealed == nil {
return &ErrBadRU{xerrors.New("nil UpdateUnsealed cid after replica update")}
}
if !commD.Equals(*si.UpdateUnsealed) {
return &ErrBadRU{xerrors.Errorf("calculated CommD differs from updated replica: %s != %s", commD, *si.UpdateUnsealed)}
}
if si.UpdateSealed == nil {
return &ErrBadRU{xerrors.Errorf("nil sealed cid")}
}
if si.ReplicaUpdateProof == nil {
return &ErrBadPR{xerrors.Errorf("nil PR2 proof")}
}
return nil
}