9e03fcab83
* Initial work supporting DDO pieces in lotus-miner * sealing: Update pipeline input to operate on UniversalPiece * sealing: Update pipeline checks/sealing states to operate on UniversalPiece * sealing: Make pipeline build with UniversalPiece * move PieceDealInfo out of api * make gen * make sealing pipeline unit tests pass * fix itest ensemble build * don't panic in SectorsStatus with deals * stop linter from complaining about checkPieces * fix sector import tests * mod tidy * sealing: Add logic for (pre)committing DDO sectors * sealing: state-types with method defs * DDO non-snap pipeline works(?), DDO Itests * DDO support in snapdeals pipeline * make gen * update actor bundles * update the gst market fix * fix: chain: use PreCommitSectorsBatch2 when setting up genesis * some bug fixes * integration working changes * update actor bundles * Make TestOnboardRawPieceSnap pass * Appease the linter * Make deadlines test pass with v12 actors * Update go-state-types, abstract market DealState * make gen * mod tidy, lint fixes * Fix some more tests * Bump version in master Bump version in master * Make gen Make gen * fix sender * fix: lotus-provider: Fix winning PoSt * fix: sql Scan cannot write to an object * Actually show miner-addrs in info-log Actually show miner-addrs in lotus-provider info-log * [WIP] feat: Add nv22 skeleton Addition of Network Version 22 skeleton * update FFI * ddo is now nv22 * make gen * temp actor bundle with ddo * use working go-state-types * gst with v13 market migration * update bundle, builtin.MethodsMiner.ProveCommitSectors2 -> 3 * actually working v13 migration, v13 migration itest * Address review * sealing: Correct DDO snap pledge math * itests: Mixed ddo itest * pipeline: Fix sectorWeight * sealing: convert market deals into PAMs in mixed sectors * sealing: make market to ddo conversion work * fix lint * update gst * Update actors and GST to lastest integ branch * commit batcher: Update ProveCommitSectors3Params builder logic * make gen * use builtin-actors master * ddo: address review * itests: Add commd assertions to ddo tests * make gen * gst with fixed types * config knobs for RequireActivationSuccess * storage: Drop obsolete flaky tasts --------- Co-authored-by: Jennifer Wang <jiayingw703@gmail.com> Co-authored-by: Aayush <arajasek94@gmail.com> Co-authored-by: Shrenuj Bansal <shrenuj.bansal@protocol.ai> Co-authored-by: Phi <orjan.roren@gmail.com> Co-authored-by: Andrew Jackson (Ajax) <snadrus@gmail.com> Co-authored-by: TippyFlits <james.bluett@protocol.ai>
303 lines
10 KiB
Go
303 lines
10 KiB
Go
package sealing
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-commp-utils/zerocomm"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/crypto"
|
|
prooftypes "github.com/filecoin-project/go-state-types/proof"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/policy"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/storage/sealer/ffiwrapper"
|
|
)
|
|
|
|
// TODO: For now we handle this by halting state execution, when we get jsonrpc reconnecting
|
|
//
|
|
// We should implement some wait-for-api logic
|
|
type ErrApi struct{ error }
|
|
|
|
type ErrNoDeals struct{ error }
|
|
type ErrInvalidDeals struct{ error }
|
|
type ErrInvalidPiece struct{ error }
|
|
type ErrExpiredDeals struct{ error }
|
|
|
|
type ErrBadCommD struct{ error }
|
|
type ErrExpiredTicket struct{ error }
|
|
type ErrBadTicket struct{ error }
|
|
type ErrPrecommitOnChain struct{ error }
|
|
type ErrSectorNumberAllocated struct{ error }
|
|
|
|
type ErrBadSeed struct{ error }
|
|
type ErrInvalidProof struct{ error }
|
|
type ErrNoPrecommit struct{ error }
|
|
type ErrCommitWaitFailed struct{ error }
|
|
|
|
type ErrBadRU struct{ error }
|
|
type ErrBadPR struct{ error }
|
|
|
|
func checkPieces(ctx context.Context, maddr address.Address, sn abi.SectorNumber, pieces []SafeSectorPiece, api SealingAPI, mustHaveDeals bool) error {
|
|
ts, err := api.ChainHead(ctx)
|
|
if err != nil {
|
|
return &ErrApi{xerrors.Errorf("getting chain head: %w", err)}
|
|
}
|
|
|
|
dealCount := 0
|
|
var offset abi.PaddedPieceSize
|
|
|
|
for i, p := range pieces {
|
|
p, i := p, i
|
|
|
|
// check that the piece is correctly aligned
|
|
if offset%p.Piece().Size != 0 {
|
|
return &ErrInvalidPiece{xerrors.Errorf("sector %d piece %d is not aligned: size=%xh offset=%xh off-by=%xh", sn, i, p.Piece().Size, offset, offset%p.Piece().Size)}
|
|
}
|
|
offset += p.Piece().Size
|
|
|
|
err := p.handleDealInfo(handleDealInfoParams{
|
|
FillerHandler: func(pi UniversalPieceInfo) error {
|
|
// if no deal is associated with the piece, ensure that we added it as
|
|
// filler (i.e. ensure that it has a zero PieceCID)
|
|
|
|
exp := zerocomm.ZeroPieceCommitment(p.Piece().Size.Unpadded())
|
|
if !p.Piece().PieceCID.Equals(exp) {
|
|
return &ErrInvalidPiece{xerrors.Errorf("sector %d piece %d had non-zero PieceCID %+v", sn, i, p.Piece().PieceCID)}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
BuiltinMarketHandler: func(pi UniversalPieceInfo) error {
|
|
dealCount++
|
|
|
|
deal, err := api.StateMarketStorageDeal(ctx, p.Impl().DealID, ts.Key())
|
|
if err != nil {
|
|
return &ErrInvalidDeals{xerrors.Errorf("getting deal %d for piece %d: %w", p.Impl().DealID, i, err)}
|
|
}
|
|
|
|
if deal.Proposal.Provider != maddr {
|
|
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with wrong provider: %s != %s", i, len(pieces), sn, p.Impl().DealID, deal.Proposal.Provider, maddr)}
|
|
}
|
|
|
|
if deal.Proposal.PieceCID != p.Piece().PieceCID {
|
|
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with wrong PieceCID: %s != %s", i, len(pieces), sn, p.Impl().DealID, p.Impl().DealProposal.PieceCID, deal.Proposal.PieceCID)}
|
|
}
|
|
|
|
if p.Piece().Size != deal.Proposal.PieceSize {
|
|
return &ErrInvalidDeals{xerrors.Errorf("piece %d (of %d) of sector %d refers deal %d with different size: %d != %d", i, len(pieces), sn, p.Impl().DealID, p.Piece().Size, deal.Proposal.PieceSize)}
|
|
}
|
|
|
|
if ts.Height() >= deal.Proposal.StartEpoch {
|
|
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.Impl().DealID, deal.Proposal.StartEpoch, ts.Height())}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
DDOHandler: func(pi UniversalPieceInfo) error {
|
|
dealCount++
|
|
|
|
// try to get allocation to see if that still works
|
|
all, err := pi.GetAllocation(ctx, api, ts.Key())
|
|
if err != nil {
|
|
return xerrors.Errorf("getting deal %d allocation: %w", p.Impl().DealID, err)
|
|
}
|
|
if all != nil {
|
|
mid, err := address.IDFromAddress(maddr)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting miner id: %w", err)
|
|
}
|
|
|
|
if all.Provider != abi.ActorID(mid) {
|
|
return xerrors.Errorf("allocation provider doesn't match miner")
|
|
}
|
|
|
|
if ts.Height() >= all.Expiration {
|
|
return &ErrExpiredDeals{xerrors.Errorf("piece allocation %d (of %d) of sector %d refers expired deal %d - should start at %d, head %d", i, len(pieces), sn, p.Impl().DealID, all.Expiration, ts.Height())}
|
|
}
|
|
|
|
if all.Size < p.Piece().Size {
|
|
return &ErrInvalidDeals{xerrors.Errorf("piece allocation %d (of %d) of sector %d refers deal %d with different size: %d != %d", i, len(pieces), sn, p.Impl().DealID, p.Piece().Size, all.Size)}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if mustHaveDeals && dealCount <= 0 {
|
|
return &ErrNoDeals{xerrors.Errorf("sector %d must have deals, but does not", sn)}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkPrecommit checks that data commitment generated in the sealing process
|
|
//
|
|
// 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) {
|
|
if err := checkPieces(ctx, maddr, si.SectorNumber, si.Pieces, api, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
if si.hasData() {
|
|
commD, err := computeUnsealedCIDFromPieces(si)
|
|
if err != nil {
|
|
return &ErrApi{xerrors.Errorf("calling StateComputeDataCommitment: %w", err)}
|
|
}
|
|
|
|
if si.CommD == nil || !commD.Equals(*si.CommD) {
|
|
return &ErrBadCommD{xerrors.Errorf("on chain CommD differs from sector: %s != %s", commD, si.CommD)}
|
|
}
|
|
}
|
|
|
|
pci, err := api.StateSectorPreCommitInfo(ctx, maddr, si.SectorNumber, tsk)
|
|
if err != nil {
|
|
return &ErrApi{xerrors.Errorf("getting precommit info: %w", err)}
|
|
}
|
|
|
|
if pci != nil {
|
|
// committed P2 message
|
|
if pci.Info.SealRandEpoch != si.TicketEpoch {
|
|
return &ErrBadTicket{xerrors.Errorf("bad ticket epoch: %d != %d", pci.Info.SealRandEpoch, si.TicketEpoch)}
|
|
}
|
|
return &ErrPrecommitOnChain{xerrors.Errorf("precommit already on chain")}
|
|
}
|
|
|
|
alloc, err := api.StateMinerSectorAllocated(ctx, maddr, si.SectorNumber, tsk)
|
|
if err != nil {
|
|
return xerrors.Errorf("checking if sector is allocated: %w", err)
|
|
}
|
|
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)}
|
|
}
|
|
|
|
//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)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Sealing) checkCommit(ctx context.Context, si SectorInfo, proof []byte, tsk types.TipSetKey) (err error) {
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
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)
|
|
}
|
|
|
|
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)}
|
|
}
|
|
|
|
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)}
|
|
}
|
|
|
|
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!")
|
|
}
|
|
|
|
ok, err := m.verif.VerifySeal(prooftypes.SealVerifyInfo{
|
|
SectorID: m.minerSectorID(si.SectorNumber),
|
|
SealedCID: pci.Info.SealedCID,
|
|
SealProof: pci.Info.SealProof,
|
|
Proof: proof,
|
|
Randomness: si.TicketValue,
|
|
InteractiveRandomness: si.SeedValue,
|
|
UnsealedCID: *si.CommD,
|
|
})
|
|
if err != nil {
|
|
return &ErrInvalidProof{xerrors.Errorf("verify seal: %w", err)}
|
|
}
|
|
if !ok {
|
|
return &ErrInvalidProof{xerrors.New("invalid proof (compute error?)")}
|
|
}
|
|
|
|
if err := checkPieces(ctx, m.maddr, si.SectorNumber, si.Pieces, m.Api, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// check that sector info is good after running a replica update
|
|
func checkReplicaUpdate(ctx context.Context, maddr address.Address, si SectorInfo, api SealingAPI) error {
|
|
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 := computeUnsealedCIDFromPieces(si)
|
|
if err != nil {
|
|
return xerrors.Errorf("computing unsealed CID from pieces: %w", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func computeUnsealedCIDFromPieces(si SectorInfo) (cid.Cid, error) {
|
|
pcs := si.pieceInfos()
|
|
return ffiwrapper.GenerateUnsealedCID(si.SectorType, pcs)
|
|
}
|