lotus/itests/kit/deals.go

336 lines
8.6 KiB
Go
Raw Normal View History

2021-05-18 21:01:10 +00:00
package kit
2019-11-06 16:10:44 +00:00
import (
2019-12-01 21:52:24 +00:00
"bytes"
2019-11-06 16:10:44 +00:00
"context"
"fmt"
2019-12-01 21:52:24 +00:00
"io/ioutil"
2019-11-07 00:18:06 +00:00
"os"
2019-12-01 21:52:24 +00:00
"path/filepath"
2019-11-06 16:10:44 +00:00
"testing"
"time"
2020-06-26 15:28:05 +00:00
"github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipld/go-car"
2021-01-19 10:07:28 +00:00
"github.com/stretchr/testify/require"
2019-11-06 19:44:28 +00:00
"github.com/filecoin-project/go-fil-markets/storagemarket"
2021-01-19 10:07:28 +00:00
"github.com/filecoin-project/go-state-types/abi"
2020-03-04 05:03:35 +00:00
"github.com/filecoin-project/lotus/api"
2019-11-30 23:17:50 +00:00
"github.com/filecoin-project/lotus/build"
2021-01-19 10:07:28 +00:00
"github.com/filecoin-project/lotus/chain/types"
2020-08-17 13:39:33 +00:00
sealing "github.com/filecoin-project/lotus/extern/storage-sealing"
2021-01-19 10:07:28 +00:00
"github.com/filecoin-project/lotus/node/impl"
ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag"
dstest "github.com/ipfs/go-merkledag/test"
unixfile "github.com/ipfs/go-unixfs/file"
2019-11-06 16:10:44 +00:00
)
2021-05-20 15:12:42 +00:00
type DealHarness struct {
t *testing.T
client api.FullNode
miner TestMiner
}
// NewDealHarness creates a test harness that contains testing utilities for deals.
func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHarness {
return &DealHarness{
t: t,
client: client,
miner: miner,
}
}
2021-06-11 04:05:20 +00:00
func (dh *DealHarness) MakeFullDealNoRetrieval(ctx context.Context, rseed int, fastRet bool, startEpoch abi.ChainEpoch) ([]byte,
*api.DealInfo, cid.Cid) {
res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0)
if err != nil {
dh.t.Fatal(err)
}
fcid := res.Root
fmt.Println("FILE CID: ", fcid)
deal := dh.StartDeal(ctx, fcid, fastRet, startEpoch)
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
dh.WaitDealSealed(ctx, deal, false, false, nil)
// Retrieval
info, err := dh.client.ClientGetDealInfo(ctx, *deal)
require.NoError(dh.t, err)
return data, info, fcid
}
2021-05-20 15:12:42 +00:00
func (dh *DealHarness) MakeFullDeal(ctx context.Context, rseed int, carExport, fastRet bool, startEpoch abi.ChainEpoch) {
res, _, data, err := CreateImportFile(ctx, dh.client, rseed, 0)
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
}
fcid := res.Root
fmt.Println("FILE CID: ", fcid)
2021-05-20 15:12:42 +00:00
deal := dh.StartDeal(ctx, fcid, fastRet, startEpoch)
2020-04-23 17:50:52 +00:00
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
dh.WaitDealSealed(ctx, deal, false, false, nil)
2020-04-23 17:50:52 +00:00
// Retrieval
2021-05-20 15:12:42 +00:00
info, err := dh.client.ClientGetDealInfo(ctx, *deal)
require.NoError(dh.t, err)
2021-05-20 15:12:42 +00:00
dh.TestRetrieval(ctx, fcid, &info.PieceCID, carExport, data)
}
2021-05-20 15:12:42 +00:00
func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid {
maddr, err := dh.miner.ActorAddress(ctx)
2020-04-23 17:50:52 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
2021-05-20 15:12:42 +00:00
addr, err := dh.client.WalletDefaultAddress(ctx)
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
}
2021-05-20 15:12:42 +00:00
deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{
Data: &storagemarket.DataRef{
TransferType: storagemarket.TTGraphsync,
Root: fcid,
},
2020-04-16 21:43:39 +00:00
Wallet: addr,
Miner: maddr,
EpochPrice: types.NewInt(1000000),
DealStartEpoch: startEpoch,
2020-07-28 17:51:47 +00:00
MinBlocksDuration: uint64(build.MinDealDuration),
2020-07-08 18:35:55 +00:00
FastRetrieval: fastRet,
2020-03-04 05:03:35 +00:00
})
2019-11-06 16:10:44 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatalf("%+v", err)
2019-11-06 16:10:44 +00:00
}
2020-04-23 17:50:52 +00:00
return deal
}
2019-11-06 19:44:28 +00:00
func (dh *DealHarness) WaitDealSealed(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) {
loop:
2019-11-06 19:44:28 +00:00
for {
2021-05-20 15:12:42 +00:00
di, err := dh.client.ClientGetDealInfo(ctx, *deal)
require.NoError(dh.t, err)
2019-11-06 20:39:07 +00:00
switch di.State {
case storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing:
if noseal {
return
}
if !noSealStart {
dh.StartSealingWaiting(ctx)
}
case storagemarket.StorageDealProposalRejected:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal rejected")
case storagemarket.StorageDealFailing:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal failed")
case storagemarket.StorageDealError:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal errored", di.Message)
case storagemarket.StorageDealActive:
2019-11-06 20:39:07 +00:00
fmt.Println("COMPLETE", di)
2019-11-07 00:18:06 +00:00
break loop
2019-11-06 20:39:07 +00:00
}
mds, err := dh.miner.MarketListIncompleteDeals(ctx)
require.NoError(dh.t, err)
var minerState storagemarket.StorageDealStatus
for _, md := range mds {
if md.DealID == di.DealID {
minerState = md.State
break
}
}
fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState])
2019-11-06 19:44:28 +00:00
time.Sleep(time.Second / 2)
if cb != nil {
cb()
}
2019-11-06 19:44:28 +00:00
}
2020-04-23 17:50:52 +00:00
}
2019-11-07 00:18:06 +00:00
2021-05-20 15:12:42 +00:00
func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) {
2020-08-06 20:16:55 +00:00
subCtx, cancel := context.WithCancel(ctx)
defer cancel()
2021-05-20 15:12:42 +00:00
updates, err := dh.miner.MarketGetDealUpdates(subCtx)
2020-08-06 20:16:55 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-08-06 20:16:55 +00:00
}
for {
select {
case <-ctx.Done():
2021-05-20 15:12:42 +00:00
dh.t.Fatal("context timeout")
2020-08-06 20:16:55 +00:00
case di := <-updates:
if deal.Equals(di.ProposalCid) {
switch di.State {
case storagemarket.StorageDealProposalRejected:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal rejected")
case storagemarket.StorageDealFailing:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal failed")
case storagemarket.StorageDealError:
2021-05-20 15:12:42 +00:00
dh.t.Fatal("deal errored", di.Message)
case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive:
fmt.Println("COMPLETE", di)
return
}
fmt.Println("Deal state: ", storagemarket.DealStates[di.State])
2020-08-06 20:16:55 +00:00
}
}
}
}
2021-05-20 15:12:42 +00:00
func (dh *DealHarness) StartSealingWaiting(ctx context.Context) {
snums, err := dh.miner.SectorsList(ctx)
require.NoError(dh.t, err)
2020-06-26 15:28:05 +00:00
for _, snum := range snums {
2021-05-20 15:12:42 +00:00
si, err := dh.miner.SectorsStatus(ctx, snum, false)
require.NoError(dh.t, err)
2020-06-26 15:28:05 +00:00
2021-05-20 15:12:42 +00:00
dh.t.Logf("Sector state: %s", si.State)
2020-06-26 15:28:05 +00:00
if si.State == api.SectorState(sealing.WaitDeals) {
2021-05-20 15:12:42 +00:00
require.NoError(dh.t, dh.miner.SectorStartSealing(ctx, snum))
2020-06-26 15:28:05 +00:00
}
flushSealingBatches(dh.t, ctx, dh.miner)
2020-06-26 15:28:05 +00:00
}
}
func (dh *DealHarness) TestRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) {
2021-05-20 15:12:42 +00:00
offers, err := dh.client.ClientFindData(ctx, fcid, piece)
2019-12-01 21:52:24 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2019-12-01 21:52:24 +00:00
}
if len(offers) < 1 {
2021-05-20 15:12:42 +00:00
dh.t.Fatal("no offers")
2019-12-01 21:52:24 +00:00
}
rpath, err := ioutil.TempDir("", "lotus-retrieve-test-")
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2019-12-01 21:52:24 +00:00
}
defer os.RemoveAll(rpath) //nolint:errcheck
2019-12-01 21:52:24 +00:00
2021-05-20 15:12:42 +00:00
caddr, err := dh.client.WalletDefaultAddress(ctx)
2019-12-01 21:52:24 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2019-12-01 21:52:24 +00:00
}
ref := &api.FileRef{
Path: filepath.Join(rpath, "ret"),
IsCAR: carExport,
}
2021-05-20 15:12:42 +00:00
updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref)
2020-10-03 00:45:15 +00:00
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-10-03 00:45:15 +00:00
}
2020-08-11 20:48:56 +00:00
for update := range updates {
if update.Err != "" {
2021-05-20 15:12:42 +00:00
dh.t.Fatalf("retrieval failed: %s", update.Err)
2020-08-11 20:04:00 +00:00
}
2019-12-01 21:52:24 +00:00
}
rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret"))
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2019-12-01 21:52:24 +00:00
}
if carExport {
2021-05-20 15:12:42 +00:00
rdata = dh.ExtractCarData(ctx, rdata, rpath)
}
if !bytes.Equal(rdata, expect) {
dh.t.Fatal("wrong expect retrieved")
2019-12-01 21:52:24 +00:00
}
2020-04-23 17:50:52 +00:00
}
2019-12-01 21:52:24 +00:00
2021-05-20 15:12:42 +00:00
func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte {
2020-04-23 17:50:52 +00:00
bserv := dstest.Bserv()
ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata))
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
b, err := bserv.GetBlock(ctx, ch.Roots[0])
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
nd, err := ipld.Decode(b)
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
dserv := dag.NewDAGService(bserv)
fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd)
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
outPath := filepath.Join(rpath, "retLoadedCAR")
if err := files.WriteTo(fil, outPath); err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
rdata, err = ioutil.ReadFile(outPath)
if err != nil {
2021-05-20 15:12:42 +00:00
dh.t.Fatal(err)
2020-04-23 17:50:52 +00:00
}
return rdata
2019-11-06 16:10:44 +00:00
}
2021-01-19 10:07:28 +00:00
2021-05-18 21:01:10 +00:00
type DealsScaffold struct {
Ctx context.Context
Client *impl.FullNodeAPI
Miner TestMiner
2021-05-18 21:01:10 +00:00
BlockMiner *BlockMiner
2021-01-19 10:07:28 +00:00
}
func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner TestMiner, clients ...api.FullNode) *BlockMiner {
2021-01-19 10:07:28 +00:00
ctx := context.Background()
2021-05-20 15:12:42 +00:00
for _, c := range clients {
addrinfo, err := c.NetAddrsListen(ctx)
if err != nil {
t.Fatal(err)
}
if err := miner.NetConnect(ctx, addrinfo); err != nil {
t.Fatal(err)
}
2021-01-19 10:07:28 +00:00
}
2021-05-20 15:12:42 +00:00
2021-01-19 10:07:28 +00:00
time.Sleep(time.Second)
2021-05-19 09:51:32 +00:00
blockMiner := NewBlockMiner(t, miner)
blockMiner.MineBlocks(ctx, blocktime)
t.Cleanup(blockMiner.Stop)
2021-05-20 15:12:42 +00:00
return blockMiner
2021-01-19 10:07:28 +00:00
}
type TestDealState int
const (
TestDealStateFailed = TestDealState(-1)
TestDealStateInProgress = TestDealState(0)
TestDealStateComplete = TestDealState(1)
)
// CategorizeDealState categorizes deal states into one of three states:
// Complete, InProgress, Failed.
func CategorizeDealState(dealStatus string) TestDealState {
switch dealStatus {
case "StorageDealFailing", "StorageDealError":
return TestDealStateFailed
case "StorageDealStaged", "StorageDealAwaitingPreCommit", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed":
return TestDealStateComplete
}
return TestDealStateInProgress
}