2021-01-25 10:28:39 +00:00
package sealing
import (
"bytes"
"context"
2022-04-12 22:31:31 +00:00
"fmt"
2021-01-25 10:28:39 +00:00
2021-09-29 20:16:19 +00:00
"github.com/filecoin-project/go-state-types/network"
2021-01-25 10:28:39 +00:00
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/types"
market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
)
type CurrentDealInfoAPI interface {
ChainGetMessage ( context . Context , cid . Cid ) ( * types . Message , error )
StateLookupID ( context . Context , address . Address , TipSetToken ) ( address . Address , error )
StateMarketStorageDeal ( context . Context , abi . DealID , TipSetToken ) ( * api . MarketDeal , error )
StateSearchMsg ( context . Context , cid . Cid ) ( * MsgLookup , error )
2021-09-29 20:16:19 +00:00
StateNetworkVersion ( ctx context . Context , tok TipSetToken ) ( network . Version , error )
2021-01-25 10:28:39 +00:00
}
type CurrentDealInfo struct {
DealID abi . DealID
MarketDeal * api . MarketDeal
PublishMsgTipSet TipSetToken
}
type CurrentDealInfoManager struct {
CDAPI CurrentDealInfoAPI
}
// GetCurrentDealInfo gets the current deal state and deal ID.
// Note that the deal ID is assigned when the deal is published, so it may
// have changed if there was a reorg after the deal was published.
func ( mgr * CurrentDealInfoManager ) GetCurrentDealInfo ( ctx context . Context , tok TipSetToken , proposal * market . DealProposal , publishCid cid . Cid ) ( CurrentDealInfo , error ) {
// Lookup the deal ID by comparing the deal proposal to the proposals in
// the publish deals message, and indexing into the message return value
dealID , pubMsgTok , err := mgr . dealIDFromPublishDealsMsg ( ctx , tok , proposal , publishCid )
if err != nil {
return CurrentDealInfo { } , err
}
// Lookup the deal state by deal ID
marketDeal , err := mgr . CDAPI . StateMarketStorageDeal ( ctx , dealID , tok )
if err == nil && proposal != nil {
// Make sure the retrieved deal proposal matches the target proposal
equal , err := mgr . CheckDealEquality ( ctx , tok , * proposal , marketDeal . Proposal )
if err != nil {
return CurrentDealInfo { } , err
}
if ! equal {
return CurrentDealInfo { } , xerrors . Errorf ( "Deal proposals for publish message %s did not match" , publishCid )
}
}
return CurrentDealInfo { DealID : dealID , MarketDeal : marketDeal , PublishMsgTipSet : pubMsgTok } , err
}
// dealIDFromPublishDealsMsg looks up the publish deals message by cid, and finds the deal ID
// by looking at the message return value
func ( mgr * CurrentDealInfoManager ) dealIDFromPublishDealsMsg ( ctx context . Context , tok TipSetToken , proposal * market . DealProposal , publishCid cid . Cid ) ( abi . DealID , TipSetToken , error ) {
dealID := abi . DealID ( 0 )
// Get the return value of the publish deals message
lookup , err := mgr . CDAPI . StateSearchMsg ( ctx , publishCid )
if err != nil {
return dealID , nil , xerrors . Errorf ( "looking for publish deal message %s: search msg failed: %w" , publishCid , err )
}
2021-07-19 00:09:57 +00:00
if lookup == nil {
2021-07-20 18:20:20 +00:00
return dealID , nil , xerrors . Errorf ( "looking for publish deal message %s: not found" , publishCid )
2021-07-19 00:09:57 +00:00
}
2021-01-25 10:28:39 +00:00
if lookup . Receipt . ExitCode != exitcode . Ok {
return dealID , nil , xerrors . Errorf ( "looking for publish deal message %s: non-ok exit code: %s" , publishCid , lookup . Receipt . ExitCode )
}
2021-09-29 20:16:19 +00:00
nv , err := mgr . CDAPI . StateNetworkVersion ( ctx , lookup . TipSetTok )
if err != nil {
return dealID , nil , xerrors . Errorf ( "getting network version: %w" , err )
}
retval , err := market . DecodePublishStorageDealsReturn ( lookup . Receipt . Return , nv )
if err != nil {
return dealID , nil , xerrors . Errorf ( "looking for publish deal message %s: decoding message return: %w" , publishCid , err )
}
dealIDs , err := retval . DealIDs ( )
if err != nil {
return dealID , nil , xerrors . Errorf ( "looking for publish deal message %s: getting dealIDs: %w" , publishCid , err )
2021-01-25 10:28:39 +00:00
}
2021-09-29 20:16:19 +00:00
// TODO: Can we delete this? We're well past the point when we first introduced the proposals into sealing deal info
2021-01-25 10:28:39 +00:00
// Previously, publish deals messages contained a single deal, and the
// deal proposal was not included in the sealing deal info.
// So check if the proposal is nil and check the number of deals published
// in the message.
if proposal == nil {
2021-09-29 20:16:19 +00:00
if len ( dealIDs ) > 1 {
2021-01-25 10:28:39 +00:00
return dealID , nil , xerrors . Errorf (
"getting deal ID from publish deal message %s: " +
"no deal proposal supplied but message return value has more than one deal (%d deals)" ,
2021-09-29 20:16:19 +00:00
publishCid , len ( dealIDs ) )
2021-01-25 10:28:39 +00:00
}
// There is a single deal in this publish message and no deal proposal
// was supplied, so we have nothing to compare against. Just assume
2021-09-29 20:16:19 +00:00
// the deal ID is correct and that it was valid
return dealIDs [ 0 ] , lookup . TipSetTok , nil
2021-01-25 10:28:39 +00:00
}
// Get the parameters to the publish deals message
pubmsg , err := mgr . CDAPI . ChainGetMessage ( ctx , publishCid )
if err != nil {
return dealID , nil , xerrors . Errorf ( "getting publish deal message %s: %w" , publishCid , err )
}
var pubDealsParams market2 . PublishStorageDealsParams
if err := pubDealsParams . UnmarshalCBOR ( bytes . NewReader ( pubmsg . Params ) ) ; err != nil {
return dealID , nil , xerrors . Errorf ( "unmarshalling publish deal message params for message %s: %w" , publishCid , err )
}
// Scan through the deal proposals in the message parameters to find the
// index of the target deal proposal
dealIdx := - 1
for i , paramDeal := range pubDealsParams . Deals {
eq , err := mgr . CheckDealEquality ( ctx , tok , * proposal , market . DealProposal ( paramDeal . Proposal ) )
if err != nil {
return dealID , nil , xerrors . Errorf ( "comparing publish deal message %s proposal to deal proposal: %w" , publishCid , err )
}
if eq {
dealIdx = i
break
}
}
2022-04-14 02:06:38 +00:00
fmt . Printf ( "found dealIdx %d\n" , dealIdx )
2021-01-25 10:28:39 +00:00
if dealIdx == - 1 {
return dealID , nil , xerrors . Errorf ( "could not find deal in publish deals message %s" , publishCid )
}
2022-04-12 18:46:01 +00:00
if dealIdx >= len ( pubDealsParams . Deals ) {
2021-01-25 10:28:39 +00:00
return dealID , nil , xerrors . Errorf (
2022-04-12 18:46:01 +00:00
"deal index %d out of bounds of deal proposals (len %d) in publish deals message %s" ,
2021-09-29 20:16:19 +00:00
dealIdx , len ( dealIDs ) , publishCid )
2021-01-25 10:28:39 +00:00
}
2022-04-12 19:34:45 +00:00
valid , outIdx , err := retval . IsDealValid ( uint64 ( dealIdx ) )
2021-09-29 20:16:19 +00:00
if err != nil {
return dealID , nil , xerrors . Errorf ( "determining deal validity: %w" , err )
}
if ! valid {
return dealID , nil , xerrors . New ( "deal was invalid at publication" )
}
2022-04-12 22:31:31 +00:00
// final check against for invalid return value output
// should not be reachable from onchain output, only pathological test cases
if outIdx >= len ( dealIDs ) {
return dealID , nil , xerrors . Errorf ( "invalid publish storage deals ret marking %d as valid while only returning %d valid deals in publish deal message %s" , outIdx , len ( dealIDs ) , publishCid )
}
2022-04-12 19:34:45 +00:00
return dealIDs [ outIdx ] , lookup . TipSetTok , nil
2021-01-25 10:28:39 +00:00
}
func ( mgr * CurrentDealInfoManager ) CheckDealEquality ( ctx context . Context , tok TipSetToken , p1 , p2 market . DealProposal ) ( bool , error ) {
p1ClientID , err := mgr . CDAPI . StateLookupID ( ctx , p1 . Client , tok )
if err != nil {
return false , err
}
p2ClientID , err := mgr . CDAPI . StateLookupID ( ctx , p2 . Client , tok )
if err != nil {
return false , err
}
return p1 . PieceCID . Equals ( p2 . PieceCID ) &&
p1 . PieceSize == p2 . PieceSize &&
p1 . VerifiedDeal == p2 . VerifiedDeal &&
p1 . Label == p2 . Label &&
p1 . StartEpoch == p2 . StartEpoch &&
p1 . EndEpoch == p2 . EndEpoch &&
p1 . StoragePricePerEpoch . Equals ( p2 . StoragePricePerEpoch ) &&
p1 . ProviderCollateral . Equals ( p2 . ProviderCollateral ) &&
p1 . ClientCollateral . Equals ( p2 . ClientCollateral ) &&
p1 . Provider == p2 . Provider &&
p1ClientID == p2ClientID , nil
}
type CurrentDealInfoTskAPI interface {
ChainGetMessage ( ctx context . Context , mc cid . Cid ) ( * types . Message , error )
StateLookupID ( context . Context , address . Address , types . TipSetKey ) ( address . Address , error )
StateMarketStorageDeal ( context . Context , abi . DealID , types . TipSetKey ) ( * api . MarketDeal , error )
2021-04-05 17:56:53 +00:00
StateSearchMsg ( ctx context . Context , from types . TipSetKey , msg cid . Cid , limit abi . ChainEpoch , allowReplaced bool ) ( * api . MsgLookup , error )
2021-09-29 20:16:19 +00:00
StateNetworkVersion ( context . Context , types . TipSetKey ) ( network . Version , error )
2021-01-25 10:28:39 +00:00
}
type CurrentDealInfoAPIAdapter struct {
CurrentDealInfoTskAPI
}
func ( c * CurrentDealInfoAPIAdapter ) StateLookupID ( ctx context . Context , a address . Address , tok TipSetToken ) ( address . Address , error ) {
tsk , err := types . TipSetKeyFromBytes ( tok )
if err != nil {
return address . Undef , xerrors . Errorf ( "failed to unmarshal TipSetToken to TipSetKey: %w" , err )
}
return c . CurrentDealInfoTskAPI . StateLookupID ( ctx , a , tsk )
}
func ( c * CurrentDealInfoAPIAdapter ) StateMarketStorageDeal ( ctx context . Context , dealID abi . DealID , tok TipSetToken ) ( * api . MarketDeal , error ) {
tsk , err := types . TipSetKeyFromBytes ( tok )
if err != nil {
return nil , xerrors . Errorf ( "failed to unmarshal TipSetToken to TipSetKey: %w" , err )
}
return c . CurrentDealInfoTskAPI . StateMarketStorageDeal ( ctx , dealID , tsk )
}
func ( c * CurrentDealInfoAPIAdapter ) StateSearchMsg ( ctx context . Context , k cid . Cid ) ( * MsgLookup , error ) {
2021-04-05 17:56:53 +00:00
wmsg , err := c . CurrentDealInfoTskAPI . StateSearchMsg ( ctx , types . EmptyTSK , k , api . LookbackNoLimit , true )
2021-01-25 10:28:39 +00:00
if err != nil {
return nil , err
}
if wmsg == nil {
return nil , nil
}
return & MsgLookup {
Receipt : MessageReceipt {
ExitCode : wmsg . Receipt . ExitCode ,
Return : wmsg . Receipt . Return ,
GasUsed : wmsg . Receipt . GasUsed ,
} ,
TipSetTok : wmsg . TipSet . Bytes ( ) ,
Height : wmsg . Height ,
} , nil
}
2021-09-29 20:16:19 +00:00
func ( c * CurrentDealInfoAPIAdapter ) StateNetworkVersion ( ctx context . Context , tok TipSetToken ) ( network . Version , error ) {
tsk , err := types . TipSetKeyFromBytes ( tok )
if err != nil {
return network . VersionMax , xerrors . Errorf ( "failed to unmarshal TipSetToken to TipSetKey: %w" , err )
}
return c . CurrentDealInfoTskAPI . StateNetworkVersion ( ctx , tsk )
}
2021-01-25 10:28:39 +00:00
var _ CurrentDealInfoAPI = ( * CurrentDealInfoAPIAdapter ) ( nil )