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-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"
2020-11-20 00:28:18 +00:00
"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"
2022-06-16 09:12:33 +00:00
"github.com/filecoin-project/lotus/chain/types"
2020-01-20 22:03:50 +00:00
)
2020-01-23 15:38:01 +00:00
// TODO: For now we handle this by halting state execution, when we get jsonrpc reconnecting
// We should implement some wait-for-api logic
2020-01-23 16:15:45 +00:00
type ErrApi struct { error }
2020-01-23 12:17:45 +00:00
2022-01-21 17:53:36 +00:00
type ErrNoDeals struct { error }
2020-01-23 16:15:45 +00:00
type ErrInvalidDeals struct { error }
2020-02-23 00:47:47 +00:00
type ErrInvalidPiece struct { error }
2020-01-23 16:15:45 +00:00
type ErrExpiredDeals struct { error }
2020-01-23 12:17:45 +00:00
2020-01-23 16:15: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 }
2020-08-18 16:02:13 +00:00
type ErrSectorNumberAllocated struct { error }
2020-01-23 14:05:44 +00:00
2020-04-03 17:45:48 +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 }
2020-08-18 16:02:13 +00:00
type ErrCommitWaitFailed struct { error }
2020-04-03 17:45:48 +00:00
2021-12-08 17:11:19 +00:00
type ErrBadRU struct { error }
type ErrBadPR struct { error }
2022-01-21 17:53:36 +00:00
func checkPieces ( ctx context . Context , maddr address . Address , si SectorInfo , api SealingAPI , mustHaveDeals bool ) error {
2022-06-16 11:15:49 +00:00
ts , err := api . ChainHead ( ctx )
2020-01-23 12:17:45 +00:00
if err != nil {
2020-01-23 15:57:14 +00:00
return & ErrApi { xerrors . Errorf ( "getting chain head: %w" , err ) }
2020-01-23 12:17:45 +00:00
}
2022-01-21 17:53:36 +00:00
dealCount := 0
2020-04-08 14:56:35 +00:00
for i , p := range si . Pieces {
2020-04-07 21:44:33 +00:00
// if no deal is associated with the piece, ensure that we added it as
// filler (i.e. ensure that it has a zero PieceCID)
2020-04-08 14:56:35 +00:00
if p . DealInfo == nil {
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" , si . SectorNumber , i , p . Piece . PieceCID ) }
2020-02-23 00:47:47 +00:00
}
continue
}
2020-04-07 21:44:33 +00:00
2022-01-21 17:53:36 +00:00
dealCount ++
2022-06-16 11:33:59 +00:00
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
}
2022-06-16 11:33:59 +00:00
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 ( si . Pieces ) , si . SectorNumber , p . DealInfo . DealID , deal . Proposal . Provider , maddr ) }
2020-08-27 11:51:13 +00:00
}
2022-06-16 11:33:59 +00:00
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 ( si . Pieces ) , si . SectorNumber , p . DealInfo . DealID , p . Piece . PieceCID , deal . Proposal . PieceCID ) }
2020-01-20 22:03:50 +00:00
}
2022-06-16 11:33:59 +00:00
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 ( si . Pieces ) , si . SectorNumber , p . DealInfo . DealID , p . Piece . Size , deal . Proposal . PieceSize ) }
2020-01-23 12:17:45 +00:00
}
2022-06-16 11:33:59 +00:00
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 ( si . Pieces ) , si . SectorNumber , p . DealInfo . DealID , deal . Proposal . StartEpoch , ts . Height ( ) ) }
2020-01-20 22:03:50 +00:00
}
}
2022-01-21 17:53:36 +00:00
if mustHaveDeals && dealCount <= 0 {
return & ErrNoDeals { ( xerrors . Errorf ( "sector %d must have deals, but does not" , si . SectorNumber ) ) }
}
2020-01-20 22:03:50 +00:00
return nil
}
2020-04-03 17:45:48 +00:00
// checkPrecommit checks that data commitment generated in the sealing process
2020-01-24 20:15:02 +00:00
// matches pieces, and that the seal ticket isn't expired
2022-06-16 09:12:33 +00:00
func checkPrecommit ( ctx context . Context , maddr address . Address , si SectorInfo , tok types . TipSetKey , height abi . ChainEpoch , api SealingAPI ) ( err error ) {
2022-01-21 17:53:36 +00:00
if err := checkPieces ( ctx , maddr , si , api , false ) ; err != nil {
2020-08-27 11:51:13 +00:00
return err
}
2022-06-16 14:05:56 +00:00
commD , err := api . StateComputeDataCID ( ctx , maddr , si . SectorType , si . dealIDs ( ) , tok )
2020-01-20 22:03:50 +00:00
if err != nil {
2020-04-06 18:07:26 +00:00
return & ErrApi { xerrors . Errorf ( "calling StateComputeDataCommitment: %w" , err ) }
2020-02-23 00:47:47 +00:00
}
2020-08-21 16:31:28 +00:00
if si . CommD == nil || ! commD . Equals ( * si . CommD ) {
2020-04-06 18:07:26 +00:00
return & ErrBadCommD { xerrors . Errorf ( "on chain CommD differs from sector: %s != %s" , commD , si . CommD ) }
2020-01-23 14:05:44 +00:00
}
2020-06-02 21:45:28 +00:00
pci , err := api . StateSectorPreCommitInfo ( ctx , maddr , si . SectorNumber , tok )
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
}
2022-06-16 12:19:53 +00:00
alloc , err := api . StateMinerSectorAllocated ( ctx , maddr , si . SectorNumber , tok )
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 ) }
}
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
2020-04-03 17:45:48 +00:00
}
2022-06-16 09:12:33 +00:00
func ( m * Sealing ) checkCommit ( ctx context . Context , si SectorInfo , proof [ ] byte , tok types . TipSetKey ) ( err error ) {
2020-04-06 20:23:37 +00:00
if si . SeedEpoch == 0 {
2020-04-03 17:45:48 +00:00
return & ErrBadSeed { xerrors . Errorf ( "seed epoch was not set" ) }
}
2021-08-18 10:43:44 +00:00
pci , err := m . Api . StateSectorPreCommitInfo ( ctx , m . maddr , si . SectorNumber , tok )
2020-04-04 02:55:19 +00:00
if err != nil {
return xerrors . Errorf ( "getting precommit info: %w" , err )
}
2020-06-17 15:19:36 +00:00
if pci == nil {
2022-06-16 12:19:53 +00:00
alloc , err := m . Api . StateMinerSectorAllocated ( ctx , m . maddr , si . SectorNumber , tok )
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 )
}
2020-06-17 15:19:36 +00:00
return & ErrNoPrecommit { xerrors . Errorf ( "precommit info not found on-chain" ) }
}
2020-09-23 19:24:51 +00:00
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-04 02:55:19 +00:00
}
2020-04-20 18:21:11 +00:00
buf := new ( bytes . Buffer )
if err := m . maddr . MarshalCBOR ( buf ) ; err != nil {
return err
}
2021-09-12 02:24:53 +00:00
seed , err := m . Api . StateGetRandomnessFromBeacon ( ctx , crypto . DomainSeparationTag_InteractiveSealChallengeSeed , si . SeedEpoch , buf . Bytes ( ) , tok )
2020-04-03 17:45:48 +00:00
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 ) {
2020-04-03 17:45:48 +00:00
return & ErrBadSeed { xerrors . Errorf ( "seed has changed" ) }
}
2020-04-04 02:55:19 +00:00
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 {
2020-11-04 20:29:08 +00:00
SectorID : m . minerSectorID ( si . SectorNumber ) ,
2020-05-22 01:03:34 +00:00
SealedCID : pci . Info . SealedCID ,
2020-11-04 20:29:08 +00:00
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 {
2020-08-17 07:35:22 +00:00
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-01-21 17:53:36 +00:00
if err := checkPieces ( ctx , m . maddr , si , m . Api , false ) ; err != nil {
2020-08-27 11:51:13 +00:00
return err
}
2020-04-03 17:45:48 +00:00
return nil
2020-01-20 22:03:50 +00:00
}
2021-12-08 17:11:19 +00:00
// check that sector info is good after running a replica update
2022-06-16 09:12:33 +00:00
func checkReplicaUpdate ( ctx context . Context , maddr address . Address , si SectorInfo , tok types . TipSetKey , api SealingAPI ) error {
2021-12-08 17:11:19 +00:00
2022-01-21 17:53:36 +00:00
if err := checkPieces ( ctx , maddr , si , api , true ) ; err != nil {
2021-12-08 17:11:19 +00:00
return err
}
if ! si . CCUpdate {
return xerrors . Errorf ( "replica update on sector not marked for update" )
}
2022-06-16 14:05:56 +00:00
commD , err := api . StateComputeDataCID ( ctx , maddr , si . SectorType , si . dealIDs ( ) , tok )
2021-12-08 17:11:19 +00:00
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 ) }
2021-12-08 17:11:19 +00:00
}
if si . UpdateSealed == nil {
return & ErrBadRU { xerrors . Errorf ( "nil sealed cid" ) }
}
if si . ReplicaUpdateProof == nil {
2022-01-18 23:54:33 +00:00
return & ErrBadPR { xerrors . Errorf ( "nil PR2 proof" ) }
2021-12-08 17:11:19 +00:00
}
return nil
}