2022-08-29 14:25:30 +00:00
// stm: #integration
2021-06-04 11:20:34 +00:00
package itests
import (
"context"
"fmt"
"io"
"os"
"testing"
"time"
2023-03-25 07:33:05 +00:00
blocks "github.com/ipfs/go-block-format"
2022-06-14 15:00:51 +00:00
"github.com/ipfs/go-cid"
"github.com/ipld/go-car"
"github.com/stretchr/testify/require"
2021-11-10 16:51:16 +00:00
"golang.org/x/xerrors"
2021-06-04 11:20:34 +00:00
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/itests/kit"
)
// use the mainnet carfile as text fixture: it will always be here
// https://dweb.link/ipfs/bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2/8/1/8/1/0/1/0
var (
2021-10-04 21:16:02 +00:00
sourceCar = "../build/genesis/mainnet.car"
carRoot , _ = cid . Parse ( "bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2" )
carCommp , _ = cid . Parse ( "baga6ea4seaqmrivgzei3fmx5qxtppwankmtou6zvigyjaveu3z2zzwhysgzuina" )
2021-11-29 19:52:55 +00:00
selectedCid , _ = cid . Parse ( "bafkqaetgnfwc6mjpon2g64tbm5sxa33xmvza" )
2021-10-04 21:16:02 +00:00
carPieceSize = abi . PaddedPieceSize ( 2097152 )
2021-11-11 15:17:39 +00:00
textSelector = api . Selector ( "8/1/8/1/0/1/0" )
textSelectorNonLink = api . Selector ( "8/1/8/1/0/1" )
textSelectorNonexistent = api . Selector ( "42" )
2021-10-04 21:16:02 +00:00
expectedResult = "fil/1/storagepower"
2021-06-04 11:20:34 +00:00
)
func TestPartialRetrieval ( t * testing . T ) {
2021-12-13 12:41:04 +00:00
//stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001,
//stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01
//stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001
//stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001
2021-12-14 10:33:33 +00:00
//stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001
2021-12-16 13:36:02 +00:00
//stm: @CLIENT_RETRIEVAL_RETRIEVE_001
2021-06-04 11:20:34 +00:00
ctx := context . Background ( )
kit . QuietMiningLogs ( )
client , miner , ens := kit . EnsembleMinimal ( t , kit . ThroughRPC ( ) , kit . MockProofs ( ) , kit . SectorSize ( 512 << 20 ) )
dh := kit . NewDealHarness ( t , client , miner , miner )
ens . InterconnectAll ( ) . BeginMining ( 50 * time . Millisecond )
_ , err := client . ClientImport ( ctx , api . FileRef { Path : sourceCar , IsCAR : true } )
require . NoError ( t , err )
caddr , err := client . WalletDefaultAddress ( ctx )
require . NoError ( t , err )
// first test retrieval from local car, then do an actual deal
2021-11-29 20:14:00 +00:00
for _ , exportMerkleProof := range [ ] bool { false , true } {
2021-11-29 19:52:55 +00:00
for _ , fullCycle := range [ ] bool { false , true } {
var retOrder api . RetrievalOrder
var eref api . ExportRef
if ! fullCycle {
eref . FromLocalCAR = sourceCar
} else {
dp := dh . DefaultStartDealParams ( )
dp . Data = & storagemarket . DataRef {
// FIXME: figure out how to do this with an online partial transfer
TransferType : storagemarket . TTManual ,
Root : carRoot ,
PieceCid : & carCommp ,
PieceSize : carPieceSize . Unpadded ( ) ,
}
proposalCid := dh . StartDeal ( ctx , dp )
// Wait for the deal to reach StorageDealCheckForAcceptance on the client
cd , err := client . ClientGetDealInfo ( ctx , * proposalCid )
require . NoError ( t , err )
require . Eventually ( t , func ( ) bool {
cd , _ := client . ClientGetDealInfo ( ctx , * proposalCid )
return cd . State == storagemarket . StorageDealCheckForAcceptance
} , 30 * time . Second , 1 * time . Second , "actual deal status is %s" , storagemarket . DealStates [ cd . State ] )
err = miner . DealsImportData ( ctx , * proposalCid , sourceCar )
require . NoError ( t , err )
// Wait for the deal to be published, we should be able to start retrieval right away
dh . WaitDealPublished ( ctx , proposalCid )
offers , err := client . ClientFindData ( ctx , carRoot , nil )
require . NoError ( t , err )
require . NotEmpty ( t , offers , "no offers" )
retOrder = offers [ 0 ] . Order ( caddr )
2021-06-04 11:20:34 +00:00
}
2021-11-29 19:52:55 +00:00
retOrder . DataSelector = & textSelector
eref . DAGs = append ( eref . DAGs , api . DagSpec {
2021-11-29 20:14:00 +00:00
DataSelector : & textSelector ,
ExportMerkleProof : exportMerkleProof ,
2021-11-29 19:52:55 +00:00
} )
eref . Root = carRoot
// test retrieval of either data or constructing a partial selective-car
for _ , retrieveAsCar := range [ ] bool { false , true } {
2022-06-29 17:07:29 +00:00
outFile := t . TempDir ( ) + string ( os . PathSeparator ) + "ret-file" + retOrder . Root . String ( )
2021-11-29 19:52:55 +00:00
require . NoError ( t , testGenesisRetrieval (
ctx ,
client ,
retOrder ,
eref ,
& api . FileRef {
2022-06-29 17:07:29 +00:00
Path : outFile ,
2021-11-29 19:52:55 +00:00
IsCAR : retrieveAsCar ,
} ,
) )
// UGH if I do not sleep here, I get things like:
/ *
retrieval failed : Retrieve failed : there is an active retrieval deal with peer 12 D3KooWK9fB9a3HZ4PQLVmEQ6pweMMn5CAyKtumB71CPTnuBDi6 for payload CID bafy2bzacecnamqgqmifpluoeldx7zzglxcljo6oja4vrmtj7432rphldpdmm2 ( retrieval deal ID 1631259332180384709 , state DealStatusFinalizingBlockstore ) - existing deal must be cancelled before starting a new retrieval deal :
github . com / filecoin - project / lotus / node / impl / client . ( * API ) . ClientRetrieve
/ home / circleci / project / node / impl / client / client . go : 774
* /
time . Sleep ( time . Second )
}
2021-06-04 11:20:34 +00:00
}
}
2021-10-04 21:16:02 +00:00
// ensure non-existent paths fail
require . EqualError (
t ,
testGenesisRetrieval (
ctx ,
client ,
api . RetrievalOrder {
2021-11-11 15:17:39 +00:00
Root : carRoot ,
DataSelector : & textSelectorNonexistent ,
2021-10-04 21:16:02 +00:00
} ,
2021-11-10 16:51:16 +00:00
api . ExportRef {
2021-11-11 15:17:54 +00:00
Root : carRoot ,
FromLocalCAR : sourceCar ,
DAGs : [ ] api . DagSpec { { DataSelector : & textSelectorNonexistent } } ,
2021-10-04 21:16:02 +00:00
} ,
& api . FileRef { } ,
) ,
2021-11-11 15:17:39 +00:00
fmt . Sprintf ( "parsing dag spec: path selection does not match a node within %s" , carRoot ) ,
2021-10-04 21:16:02 +00:00
)
// ensure non-boundary retrievals fail
require . EqualError (
t ,
testGenesisRetrieval (
ctx ,
client ,
api . RetrievalOrder {
2021-11-11 15:17:39 +00:00
Root : carRoot ,
DataSelector : & textSelectorNonLink ,
2021-10-04 21:16:02 +00:00
} ,
2021-11-10 16:51:16 +00:00
api . ExportRef {
2021-11-11 15:17:54 +00:00
Root : carRoot ,
FromLocalCAR : sourceCar ,
DAGs : [ ] api . DagSpec { { DataSelector : & textSelectorNonLink } } ,
2021-10-04 21:16:02 +00:00
} ,
& api . FileRef { } ,
) ,
2021-11-11 15:17:39 +00:00
fmt . Sprintf ( "parsing dag spec: error while locating partial retrieval sub-root: unsupported selection path '%s' does not correspond to a block boundary (a.k.a. CID link)" , textSelectorNonLink ) ,
2021-10-04 21:16:02 +00:00
)
2021-06-04 11:20:34 +00:00
}
2022-06-29 17:07:29 +00:00
func testGenesisRetrieval ( ctx context . Context , client * kit . TestFullNode , retOrder api . RetrievalOrder , eref api . ExportRef , retRef * api . FileRef ) error {
2021-06-04 11:20:34 +00:00
if retOrder . Total . Nil ( ) {
retOrder . Total = big . Zero ( )
}
if retOrder . UnsealPrice . Nil ( ) {
retOrder . UnsealPrice = big . Zero ( )
}
2021-11-10 16:51:16 +00:00
if eref . FromLocalCAR == "" {
rr , err := client . ClientRetrieve ( ctx , retOrder )
if err != nil {
return err
}
eref . DealID = rr . DealID
if err := client . ClientRetrieveWait ( ctx , rr . DealID ) ; err != nil {
return xerrors . Errorf ( "retrieval wait: %w" , err )
}
}
err := client . ClientExport ( ctx , eref , * retRef )
2021-06-04 11:20:34 +00:00
if err != nil {
return err
}
2022-06-29 17:07:29 +00:00
outFile , err := os . Open ( retRef . Path )
if err != nil {
return err
}
defer outFile . Close ( ) //nolint:errcheck
2021-06-04 11:20:34 +00:00
var data [ ] byte
if ! retRef . IsCAR {
data , err = io . ReadAll ( outFile )
if err != nil {
return err
}
} else {
cr , err := car . NewCarReader ( outFile )
if err != nil {
return err
}
if len ( cr . Header . Roots ) != 1 {
return fmt . Errorf ( "expected a single root in result car, got %d" , len ( cr . Header . Roots ) )
2021-11-29 20:14:00 +00:00
} else if eref . DAGs [ 0 ] . ExportMerkleProof && cr . Header . Roots [ 0 ] . String ( ) != carRoot . String ( ) {
2021-06-04 11:20:34 +00:00
return fmt . Errorf ( "expected root cid '%s', got '%s'" , carRoot . String ( ) , cr . Header . Roots [ 0 ] . String ( ) )
2021-11-29 20:14:00 +00:00
} else if ! eref . DAGs [ 0 ] . ExportMerkleProof && cr . Header . Roots [ 0 ] . String ( ) != selectedCid . String ( ) {
2021-11-29 19:52:55 +00:00
return fmt . Errorf ( "expected root cid '%s', got '%s'" , selectedCid . String ( ) , cr . Header . Roots [ 0 ] . String ( ) )
2021-06-04 11:20:34 +00:00
}
blks := make ( [ ] blocks . Block , 0 )
for {
b , err := cr . Next ( )
if err == io . EOF {
break
} else if err != nil {
return err
}
blks = append ( blks , b )
}
2021-11-29 20:14:00 +00:00
if ( eref . DAGs [ 0 ] . ExportMerkleProof && len ( blks ) != 3 ) || ( ! eref . DAGs [ 0 ] . ExportMerkleProof && len ( blks ) != 1 ) {
2021-11-29 19:52:55 +00:00
return fmt . Errorf ( "expected a car file with 3/1 blocks, got one with %d instead" , len ( blks ) )
2021-06-04 11:20:34 +00:00
}
2021-11-29 19:52:55 +00:00
data = blks [ len ( blks ) - 1 ] . RawData ( )
2021-06-04 11:20:34 +00:00
}
if string ( data ) != expectedResult {
return fmt . Errorf ( "retrieved data mismatch: expected '%s' got '%s'" , expectedResult , data )
}
return nil
}