Merge pull request #4497 from filecoin-project/feat/lite-market-client-cli
lotus-lite: CLI tests for `lotus client` commands
This commit is contained in:
commit
1ec8252412
@ -34,5 +34,6 @@ type GatewayAPI interface {
|
||||
StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*MarketDeal, error)
|
||||
StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error)
|
||||
StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error)
|
||||
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
||||
StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*MsgLookup, error)
|
||||
}
|
||||
|
@ -396,6 +396,7 @@ type GatewayStruct struct {
|
||||
StateMarketBalance func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error)
|
||||
StateMarketStorageDeal func(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error)
|
||||
StateNetworkVersion func(ctx context.Context, tsk types.TipSetKey) (stnetwork.Version, error)
|
||||
StateVerifiedClientStatus func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
||||
StateWaitMsg func(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error)
|
||||
}
|
||||
}
|
||||
@ -1547,6 +1548,10 @@ func (g GatewayStruct) StateNetworkVersion(ctx context.Context, tsk types.TipSet
|
||||
return g.Internal.StateNetworkVersion(ctx, tsk)
|
||||
}
|
||||
|
||||
func (g GatewayStruct) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) {
|
||||
return g.Internal.StateVerifiedClientStatus(ctx, addr, tsk)
|
||||
}
|
||||
|
||||
func (g GatewayStruct) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) {
|
||||
return g.Internal.StateWaitMsg(ctx, msg, confidence)
|
||||
}
|
||||
|
@ -106,21 +106,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
}
|
||||
|
||||
func MakeDeal(t *testing.T, ctx context.Context, rseed int, client api.FullNode, miner TestStorageNode, carExport, fastRet bool) {
|
||||
data := make([]byte, 1600)
|
||||
rand.New(rand.NewSource(int64(rseed))).Read(data)
|
||||
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, "sourcefile.dat")
|
||||
err = ioutil.WriteFile(path, data, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := client.ClientImport(ctx, api.FileRef{Path: path})
|
||||
res, data, err := CreateClientFile(ctx, client, rseed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -141,6 +127,28 @@ func MakeDeal(t *testing.T, ctx context.Context, rseed int, client api.FullNode,
|
||||
testRetrieval(t, ctx, client, fcid, &info.PieceCID, carExport, data)
|
||||
}
|
||||
|
||||
func CreateClientFile(ctx context.Context, client api.FullNode, rseed int) (*api.ImportRes, []byte, error) {
|
||||
data := make([]byte, 1600)
|
||||
rand.New(rand.NewSource(int64(rseed))).Read(data)
|
||||
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, "sourcefile.dat")
|
||||
err = ioutil.WriteFile(path, data, 0644)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res, err := client.ClientImport(ctx, api.FileRef{Path: path})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return res, data, nil
|
||||
}
|
||||
|
||||
func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -343,6 +343,7 @@ var clientDealCmd = &cli.Command{
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
|
||||
if cctx.NArg() != 4 {
|
||||
return xerrors.New("expected 4 args: dataCid, miner, price, duration")
|
||||
@ -462,7 +463,7 @@ var clientDealCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(encoder.Encode(*proposal))
|
||||
afmt.Println(encoder.Encode(*proposal))
|
||||
|
||||
return nil
|
||||
},
|
||||
@ -477,6 +478,7 @@ func interactiveDeal(cctx *cli.Context) error {
|
||||
ctx := ReqContext(cctx)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
|
||||
state := "import"
|
||||
gib := types.NewInt(1 << 30)
|
||||
@ -517,10 +519,10 @@ func interactiveDeal(cctx *cli.Context) error {
|
||||
}
|
||||
|
||||
printErr := func(err error) {
|
||||
fmt.Printf("%s %s\n", color.RedString("Error:"), err.Error())
|
||||
afmt.Printf("%s %s\n", color.RedString("Error:"), err.Error())
|
||||
}
|
||||
|
||||
cs := readline.NewCancelableStdin(os.Stdin)
|
||||
cs := readline.NewCancelableStdin(afmt.Stdin)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
cs.Close() // nolint:errcheck
|
||||
@ -537,7 +539,7 @@ uiLoop:
|
||||
|
||||
switch state {
|
||||
case "import":
|
||||
fmt.Print("Data CID (from " + color.YellowString("lotus client import") + "): ")
|
||||
afmt.Print("Data CID (from " + color.YellowString("lotus client import") + "): ")
|
||||
|
||||
_cidStr, _, err := rl.ReadLine()
|
||||
cidStr := string(_cidStr)
|
||||
@ -560,7 +562,7 @@ uiLoop:
|
||||
|
||||
state = "duration"
|
||||
case "duration":
|
||||
fmt.Print("Deal duration (days): ")
|
||||
afmt.Print("Deal duration (days): ")
|
||||
|
||||
_daystr, _, err := rl.ReadLine()
|
||||
daystr := string(_daystr)
|
||||
@ -605,7 +607,7 @@ uiLoop:
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Print("\nMake this a verified deal? (yes/no): ")
|
||||
afmt.Print("\nMake this a verified deal? (yes/no): ")
|
||||
|
||||
_yn, _, err := rl.ReadLine()
|
||||
yn := string(_yn)
|
||||
@ -619,13 +621,13 @@ uiLoop:
|
||||
case "no":
|
||||
verified = false
|
||||
default:
|
||||
fmt.Println("Type in full 'yes' or 'no'")
|
||||
afmt.Println("Type in full 'yes' or 'no'")
|
||||
continue
|
||||
}
|
||||
|
||||
state = "miner"
|
||||
case "miner":
|
||||
fmt.Print("Miner Addresses (f0.. f0..), none to find: ")
|
||||
afmt.Print("Miner Addresses (f0.. f0..), none to find: ")
|
||||
|
||||
_maddrsStr, _, err := rl.ReadLine()
|
||||
maddrsStr := string(_maddrsStr)
|
||||
@ -664,11 +666,11 @@ uiLoop:
|
||||
candidateAsks = append(candidateAsks, ask)
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d candidate asks\n", len(candidateAsks))
|
||||
afmt.Printf("Found %d candidate asks\n", len(candidateAsks))
|
||||
state = "find-budget"
|
||||
case "find-budget":
|
||||
fmt.Printf("Proposing from %s, Current Balance: %s\n", a, types.FIL(fromBal))
|
||||
fmt.Print("Maximum budget (FIL): ") // TODO: Propose some default somehow?
|
||||
afmt.Printf("Proposing from %s, Current Balance: %s\n", a, types.FIL(fromBal))
|
||||
afmt.Print("Maximum budget (FIL): ") // TODO: Propose some default somehow?
|
||||
|
||||
_budgetStr, _, err := rl.ReadLine()
|
||||
budgetStr := string(_budgetStr)
|
||||
@ -698,10 +700,10 @@ uiLoop:
|
||||
}
|
||||
}
|
||||
candidateAsks = goodAsks
|
||||
fmt.Printf("%d asks within budget\n", len(candidateAsks))
|
||||
afmt.Printf("%d asks within budget\n", len(candidateAsks))
|
||||
state = "find-count"
|
||||
case "find-count":
|
||||
fmt.Print("Deals to make (1): ")
|
||||
afmt.Print("Deals to make (1): ")
|
||||
dealcStr, _, err := rl.ReadLine()
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("reading deal count: %w", err))
|
||||
@ -780,12 +782,12 @@ uiLoop:
|
||||
case "confirm":
|
||||
// TODO: do some more or epochs math (round to miner PP, deal start buffer)
|
||||
|
||||
fmt.Printf("-----\n")
|
||||
fmt.Printf("Proposing from %s\n", a)
|
||||
fmt.Printf("\tBalance: %s\n", types.FIL(fromBal))
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("Piece size: %s (Payload size: %s)\n", units.BytesSize(float64(ds.PieceSize)), units.BytesSize(float64(ds.PayloadSize)))
|
||||
fmt.Printf("Duration: %s\n", dur)
|
||||
afmt.Printf("-----\n")
|
||||
afmt.Printf("Proposing from %s\n", a)
|
||||
afmt.Printf("\tBalance: %s\n", types.FIL(fromBal))
|
||||
afmt.Printf("\n")
|
||||
afmt.Printf("Piece size: %s (Payload size: %s)\n", units.BytesSize(float64(ds.PieceSize)), units.BytesSize(float64(ds.PayloadSize)))
|
||||
afmt.Printf("Duration: %s\n", dur)
|
||||
|
||||
pricePerGib := big.Zero()
|
||||
for _, a := range ask {
|
||||
@ -804,7 +806,7 @@ uiLoop:
|
||||
|
||||
if len(ask) > 1 {
|
||||
totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs)))
|
||||
fmt.Printf("Miner %s (Power:%s) price: ~%s (%s per epoch)\n", color.YellowString(a.Miner.String()), color.GreenString(types.SizeStr(mpow.MinerPower.QualityAdjPower)), color.BlueString(types.FIL(totalPrice).String()), types.FIL(epochPrice))
|
||||
afmt.Printf("Miner %s (Power:%s) price: ~%s (%s per epoch)\n", color.YellowString(a.Miner.String()), color.GreenString(types.SizeStr(mpow.MinerPower.QualityAdjPower)), color.BlueString(types.FIL(totalPrice).String()), types.FIL(epochPrice))
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,12 +814,12 @@ uiLoop:
|
||||
epochPrice := types.BigDiv(types.BigMul(pricePerGib, types.NewInt(uint64(ds.PieceSize))), gib)
|
||||
totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs)))
|
||||
|
||||
fmt.Printf("Total price: ~%s (%s per epoch)\n", color.CyanString(types.FIL(totalPrice).String()), types.FIL(epochPrice))
|
||||
fmt.Printf("Verified: %v\n", verified)
|
||||
afmt.Printf("Total price: ~%s (%s per epoch)\n", color.CyanString(types.FIL(totalPrice).String()), types.FIL(epochPrice))
|
||||
afmt.Printf("Verified: %v\n", verified)
|
||||
|
||||
state = "accept"
|
||||
case "accept":
|
||||
fmt.Print("\nAccept (yes/no): ")
|
||||
afmt.Print("\nAccept (yes/no): ")
|
||||
|
||||
_yn, _, err := rl.ReadLine()
|
||||
yn := string(_yn)
|
||||
@ -830,7 +832,7 @@ uiLoop:
|
||||
}
|
||||
|
||||
if yn != "yes" {
|
||||
fmt.Println("Type in full 'yes' or 'no'")
|
||||
afmt.Println("Type in full 'yes' or 'no'")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -861,7 +863,7 @@ uiLoop:
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Deal (%s) CID: %s\n", maddr, color.GreenString(encoder.Encode(*proposal)))
|
||||
afmt.Printf("Deal (%s) CID: %s\n", maddr, color.GreenString(encoder.Encode(*proposal)))
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -975,6 +977,7 @@ var clientRetrieveCmd = &cli.Command{
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
|
||||
var payer address.Address
|
||||
if cctx.String("from") != "" {
|
||||
@ -1083,14 +1086,14 @@ var clientRetrieveCmd = &cli.Command{
|
||||
select {
|
||||
case evt, ok := <-updates:
|
||||
if ok {
|
||||
fmt.Printf("> Recv: %s, Paid %s, %s (%s)\n",
|
||||
afmt.Printf("> Recv: %s, Paid %s, %s (%s)\n",
|
||||
types.SizeStr(types.NewInt(evt.BytesReceived)),
|
||||
types.FIL(evt.FundsSpent),
|
||||
retrievalmarket.ClientEvents[evt.Event],
|
||||
retrievalmarket.DealStatuses[evt.Status],
|
||||
)
|
||||
} else {
|
||||
fmt.Println("Success")
|
||||
afmt.Println("Success")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1269,8 +1272,9 @@ var clientQueryAskCmd = &cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
if cctx.NArg() != 1 {
|
||||
fmt.Println("Usage: query-ask [minerAddress]")
|
||||
afmt.Println("Usage: query-ask [minerAddress]")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1311,23 +1315,23 @@ var clientQueryAskCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Ask: %s\n", maddr)
|
||||
fmt.Printf("Price per GiB: %s\n", types.FIL(ask.Price))
|
||||
fmt.Printf("Verified Price per GiB: %s\n", types.FIL(ask.VerifiedPrice))
|
||||
fmt.Printf("Max Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))))
|
||||
afmt.Printf("Ask: %s\n", maddr)
|
||||
afmt.Printf("Price per GiB: %s\n", types.FIL(ask.Price))
|
||||
afmt.Printf("Verified Price per GiB: %s\n", types.FIL(ask.VerifiedPrice))
|
||||
afmt.Printf("Max Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))))
|
||||
|
||||
size := cctx.Int64("size")
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
perEpoch := types.BigDiv(types.BigMul(ask.Price, types.NewInt(uint64(size))), types.NewInt(1<<30))
|
||||
fmt.Printf("Price per Block: %s\n", types.FIL(perEpoch))
|
||||
afmt.Printf("Price per Block: %s\n", types.FIL(perEpoch))
|
||||
|
||||
duration := cctx.Int64("duration")
|
||||
if duration == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Total Price: %s\n", types.FIL(types.BigMul(perEpoch, types.NewInt(uint64(duration)))))
|
||||
afmt.Printf("Total Price: %s\n", types.FIL(types.BigMul(perEpoch, types.NewInt(uint64(duration)))))
|
||||
|
||||
return nil
|
||||
},
|
||||
@ -1410,7 +1414,7 @@ var clientListDeals = &cli.Command{
|
||||
}
|
||||
}
|
||||
|
||||
return outputStorageDeals(ctx, os.Stdout, api, localDeals, cctx.Bool("verbose"), cctx.Bool("color"), showFailed)
|
||||
return outputStorageDeals(ctx, cctx.App.Writer, api, localDeals, cctx.Bool("verbose"), cctx.Bool("color"), showFailed)
|
||||
},
|
||||
}
|
||||
|
||||
|
22
cli/client_test.go
Normal file
22
cli/client_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clitest "github.com/filecoin-project/lotus/cli/test"
|
||||
)
|
||||
|
||||
// TestClient does a basic test to exercise the client CLI
|
||||
// commands
|
||||
func TestClient(t *testing.T) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
clitest.QuietMiningLogs()
|
||||
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
clientNode, _ := clitest.StartOneNodeOneMiner(ctx, t, blocktime)
|
||||
clitest.RunClientTest(t, Commands, clientNode)
|
||||
}
|
@ -2,15 +2,16 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
ufcli "github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type PrintHelpErr struct {
|
||||
Err error
|
||||
Ctx *cli.Context
|
||||
Ctx *ufcli.Context
|
||||
}
|
||||
|
||||
func (e *PrintHelpErr) Error() string {
|
||||
@ -26,11 +27,11 @@ func (e *PrintHelpErr) Is(o error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func ShowHelp(cctx *cli.Context, err error) error {
|
||||
func ShowHelp(cctx *ufcli.Context, err error) error {
|
||||
return &PrintHelpErr{Err: err, Ctx: cctx}
|
||||
}
|
||||
|
||||
func RunApp(app *cli.App) {
|
||||
func RunApp(app *ufcli.App) {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if os.Getenv("LOTUS_DEV") != "" {
|
||||
log.Warnf("%+v", err)
|
||||
@ -39,8 +40,40 @@ func RunApp(app *cli.App) {
|
||||
}
|
||||
var phe *PrintHelpErr
|
||||
if xerrors.As(err, &phe) {
|
||||
_ = cli.ShowCommandHelp(phe.Ctx, phe.Ctx.Command.Name)
|
||||
_ = ufcli.ShowCommandHelp(phe.Ctx, phe.Ctx.Command.Name)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type AppFmt struct {
|
||||
app *ufcli.App
|
||||
Stdin io.Reader
|
||||
}
|
||||
|
||||
func NewAppFmt(a *ufcli.App) *AppFmt {
|
||||
var stdin io.Reader
|
||||
istdin, ok := a.Metadata["stdin"]
|
||||
if ok {
|
||||
stdin = istdin.(io.Reader)
|
||||
} else {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
return &AppFmt{app: a, Stdin: stdin}
|
||||
}
|
||||
|
||||
func (a *AppFmt) Print(args ...interface{}) {
|
||||
fmt.Fprint(a.app.Writer, args...)
|
||||
}
|
||||
|
||||
func (a *AppFmt) Println(args ...interface{}) {
|
||||
fmt.Fprintln(a.app.Writer, args...)
|
||||
}
|
||||
|
||||
func (a *AppFmt) Printf(fmtstr string, args ...interface{}) {
|
||||
fmt.Fprintf(a.app.Writer, fmtstr, args...)
|
||||
}
|
||||
|
||||
func (a *AppFmt) Scan(args ...interface{}) (int, error) {
|
||||
return fmt.Fscan(a.Stdin, args...)
|
||||
}
|
||||
|
@ -6,50 +6,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/api/test"
|
||||
clitest "github.com/filecoin-project/lotus/cli/test"
|
||||
builder "github.com/filecoin-project/lotus/node/test"
|
||||
)
|
||||
|
||||
// TestMultisig does a basic test to exercise the multisig CLI
|
||||
// commands
|
||||
func TestMultisig(t *testing.T) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
clitest.QuietMiningLogs()
|
||||
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
nodes, _ := startNodes(ctx, t, blocktime)
|
||||
clientNode := nodes[0]
|
||||
clientNode, _ := clitest.StartOneNodeOneMiner(ctx, t, blocktime)
|
||||
clitest.RunMultisigTest(t, Commands, clientNode)
|
||||
}
|
||||
|
||||
func startNodes(ctx context.Context, t *testing.T, blocktime time.Duration) ([]test.TestNode, []address.Address) {
|
||||
n, sn := builder.RPCMockSbBuilder(t, test.OneFull, test.OneMiner)
|
||||
|
||||
full := n[0]
|
||||
miner := sn[0]
|
||||
|
||||
// Get everyone connected
|
||||
addrs, err := full.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := miner.NetConnect(ctx, addrs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start mining blocks
|
||||
bm := test.NewBlockMiner(ctx, t, miner, blocktime)
|
||||
bm.MineBlocks()
|
||||
|
||||
// Get the creator's address
|
||||
creatorAddr, err := full.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create mock CLI
|
||||
return n, []address.Address{creatorAddr}
|
||||
}
|
||||
|
115
cli/test/client.go
Normal file
115
cli/test/client.go
Normal file
@ -0,0 +1,115 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/lotus/api/test"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/v2/actors/builtin"
|
||||
"github.com/stretchr/testify/require"
|
||||
lcli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// RunClientTest exercises some of the client CLI commands
|
||||
func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Create mock CLI
|
||||
mockCLI := newMockCLI(t, cmds)
|
||||
clientCLI := mockCLI.client(clientNode.ListenAddr)
|
||||
|
||||
// Get the miner address
|
||||
addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, addrs, 1)
|
||||
|
||||
minerAddr := addrs[0]
|
||||
fmt.Println("Miner:", minerAddr)
|
||||
|
||||
// client query-ask <miner addr>
|
||||
cmd := []string{
|
||||
"client", "query-ask", minerAddr.String(),
|
||||
}
|
||||
out := clientCLI.runCmd(cmd)
|
||||
require.Regexp(t, regexp.MustCompile("Ask:"), out)
|
||||
|
||||
// Create a deal (non-interactive)
|
||||
// client deal <cid> <miner addr> 1000000attofil <duration>
|
||||
res, _, err := test.CreateClientFile(ctx, clientNode, 1)
|
||||
require.NoError(t, err)
|
||||
dataCid := res.Root
|
||||
price := "1000000attofil"
|
||||
duration := fmt.Sprintf("%d", build.MinDealDuration)
|
||||
cmd = []string{
|
||||
"client", "deal", dataCid.String(), minerAddr.String(), price, duration,
|
||||
}
|
||||
out = clientCLI.runCmd(cmd)
|
||||
fmt.Println("client deal", out)
|
||||
|
||||
// Create a deal (interactive)
|
||||
// client deal
|
||||
// <cid>
|
||||
// <duration> (in days)
|
||||
// <miner addr>
|
||||
// "no" (verified client)
|
||||
// "yes" (confirm deal)
|
||||
res, _, err = test.CreateClientFile(ctx, clientNode, 2)
|
||||
require.NoError(t, err)
|
||||
dataCid2 := res.Root
|
||||
duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay)
|
||||
cmd = []string{
|
||||
"client", "deal",
|
||||
}
|
||||
interactiveCmds := []string{
|
||||
dataCid2.String(),
|
||||
duration,
|
||||
minerAddr.String(),
|
||||
"no",
|
||||
"yes",
|
||||
}
|
||||
out = clientCLI.runInteractiveCmd(cmd, interactiveCmds)
|
||||
fmt.Println("client deal:\n", out)
|
||||
|
||||
// Wait for provider to start sealing deal
|
||||
dealStatus := ""
|
||||
for dealStatus != "StorageDealSealing" {
|
||||
// client list-deals
|
||||
cmd = []string{"client", "list-deals"}
|
||||
out = clientCLI.runCmd(cmd)
|
||||
fmt.Println("list-deals:\n", out)
|
||||
|
||||
lines := strings.Split(out, "\n")
|
||||
require.Len(t, lines, 2)
|
||||
re := regexp.MustCompile(`\s+`)
|
||||
parts := re.Split(lines[1], -1)
|
||||
if len(parts) < 4 {
|
||||
require.Fail(t, "bad list-deals output format")
|
||||
}
|
||||
dealStatus = parts[3]
|
||||
fmt.Println(" Deal status:", dealStatus)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
// Retrieve the first file from the miner
|
||||
// client retrieve <cid> <file path>
|
||||
tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-client")
|
||||
require.NoError(t, err)
|
||||
path := filepath.Join(tmpdir, "outfile.dat")
|
||||
cmd = []string{
|
||||
"client", "retrieve", dataCid.String(), path,
|
||||
}
|
||||
out = clientCLI.runCmd(cmd)
|
||||
fmt.Println("retrieve:\n", out)
|
||||
require.Regexp(t, regexp.MustCompile("Success"), out)
|
||||
}
|
@ -122,3 +122,12 @@ func (c *mockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet {
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func (c *mockCLIClient) runInteractiveCmd(cmd []string, interactive []string) string {
|
||||
c.toStdin(strings.Join(interactive, "\n") + "\n")
|
||||
return c.runCmd(cmd)
|
||||
}
|
||||
|
||||
func (c *mockCLIClient) toStdin(s string) {
|
||||
c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s)
|
||||
}
|
||||
|
@ -10,19 +10,10 @@ import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/api/test"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
lcli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func QuietMiningLogs() {
|
||||
logging.SetLogLevel("miner", "ERROR")
|
||||
logging.SetLogLevel("chainstore", "ERROR")
|
||||
logging.SetLogLevel("chain", "ERROR")
|
||||
logging.SetLogLevel("sub", "ERROR")
|
||||
logging.SetLogLevel("storageminer", "ERROR")
|
||||
}
|
||||
|
||||
func RunMultisigTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
41
cli/test/net.go
Normal file
41
cli/test/net.go
Normal file
@ -0,0 +1,41 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/api/test"
|
||||
test2 "github.com/filecoin-project/lotus/node/test"
|
||||
)
|
||||
|
||||
func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (test.TestNode, address.Address) {
|
||||
n, sn := test2.RPCMockSbBuilder(t, test.OneFull, test.OneMiner)
|
||||
|
||||
full := n[0]
|
||||
miner := sn[0]
|
||||
|
||||
// Get everyone connected
|
||||
addrs, err := full.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := miner.NetConnect(ctx, addrs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start mining blocks
|
||||
bm := test.NewBlockMiner(ctx, t, miner, blocktime)
|
||||
bm.MineBlocks()
|
||||
|
||||
// Get the full node's wallet address
|
||||
fullAddr, err := full.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create mock CLI
|
||||
return full, fullAddr
|
||||
}
|
12
cli/test/util.go
Normal file
12
cli/test/util.go
Normal file
@ -0,0 +1,12 @@
|
||||
package test
|
||||
|
||||
import "github.com/ipfs/go-log/v2"
|
||||
|
||||
func QuietMiningLogs() {
|
||||
_ = log.SetLogLevel("miner", "ERROR")
|
||||
_ = log.SetLogLevel("chainstore", "ERROR")
|
||||
_ = log.SetLogLevel("chain", "ERROR")
|
||||
_ = log.SetLogLevel("sub", "ERROR")
|
||||
_ = log.SetLogLevel("storageminer", "ERROR")
|
||||
_ = log.SetLogLevel("pubsub", "ERROR")
|
||||
}
|
@ -67,6 +67,7 @@ type gatewayDepsAPI interface {
|
||||
StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error)
|
||||
StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error)
|
||||
StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error)
|
||||
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
||||
StateVMCirculatingSupplyInternal(context.Context, types.TipSetKey) (api.CirculatingSupply, error)
|
||||
}
|
||||
|
||||
@ -364,6 +365,13 @@ func (a *GatewayAPI) StateCirculatingSupply(ctx context.Context, tsk types.TipSe
|
||||
|
||||
}
|
||||
|
||||
func (a *GatewayAPI) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) {
|
||||
if err := a.checkTipsetKey(ctx, tsk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.api.StateVerifiedClientStatus(ctx, addr, tsk)
|
||||
}
|
||||
|
||||
func (a *GatewayAPI) StateVMCirculatingSupplyInternal(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) {
|
||||
if err := a.checkTipsetKey(ctx, tsk); err != nil {
|
||||
return api.CirculatingSupply{}, err
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/filecoin-project/lotus/cli"
|
||||
clitest "github.com/filecoin-project/lotus/cli/test"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
|
||||
init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init"
|
||||
multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig"
|
||||
@ -45,6 +44,7 @@ func init() {
|
||||
// node that is connected through a gateway to a full API node
|
||||
func TestWalletMsig(t *testing.T) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
clitest.QuietMiningLogs()
|
||||
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
@ -155,80 +155,35 @@ func TestMsigCLI(t *testing.T) {
|
||||
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
nodes := startNodes(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit)
|
||||
nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit)
|
||||
defer nodes.closer()
|
||||
|
||||
lite := nodes.lite
|
||||
full := nodes.full
|
||||
|
||||
// The full node starts with a wallet
|
||||
fullWalletAddr, err := full.WalletDefaultAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a wallet on the lite node
|
||||
liteWalletAddr, err := lite.WalletNew(ctx, types.KTSecp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send some funds from the full node to the lite node
|
||||
err = sendFunds(ctx, full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
||||
require.NoError(t, err)
|
||||
|
||||
clitest.RunMultisigTest(t, cli.Commands, lite)
|
||||
}
|
||||
|
||||
func sendFunds(ctx context.Context, fromNode test.TestNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error {
|
||||
msg := &types.Message{
|
||||
From: fromAddr,
|
||||
To: toAddr,
|
||||
Value: amt,
|
||||
}
|
||||
|
||||
sm, err := fromNode.MpoolPushMessage(ctx, msg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := fromNode.StateWaitMsg(ctx, sm.Cid(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Receipt.ExitCode != 0 {
|
||||
return xerrors.Errorf("send funds failed with exit code %d", res.Receipt.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDealFlow(t *testing.T) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
logging.SetLogLevel("miner", "ERROR")
|
||||
logging.SetLogLevel("chainstore", "ERROR")
|
||||
logging.SetLogLevel("chain", "ERROR")
|
||||
logging.SetLogLevel("sub", "ERROR")
|
||||
logging.SetLogLevel("storageminer", "ERROR")
|
||||
clitest.QuietMiningLogs()
|
||||
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
nodes := startNodes(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit)
|
||||
nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit)
|
||||
defer nodes.closer()
|
||||
|
||||
full := nodes.full
|
||||
lite := nodes.lite
|
||||
test.MakeDeal(t, ctx, 6, nodes.lite, nodes.miner, false, false)
|
||||
}
|
||||
|
||||
// The full node starts with a wallet
|
||||
fullWalletAddr, err := full.WalletDefaultAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
func TestCLIDealFlow(t *testing.T) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
clitest.QuietMiningLogs()
|
||||
|
||||
// Create a wallet on the lite node
|
||||
liteWalletAddr, err := lite.WalletNew(ctx, types.KTSecp256k1)
|
||||
require.NoError(t, err)
|
||||
blocktime := 5 * time.Millisecond
|
||||
ctx := context.Background()
|
||||
nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit)
|
||||
defer nodes.closer()
|
||||
|
||||
// Send some funds from the full node to the lite node
|
||||
err = sendFunds(ctx, full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
||||
require.NoError(t, err)
|
||||
|
||||
test.MakeDeal(t, ctx, 6, lite, nodes.miner, false, false)
|
||||
clitest.RunClientTest(t, cli.Commands, nodes.lite)
|
||||
}
|
||||
|
||||
type testNodes struct {
|
||||
@ -238,6 +193,30 @@ type testNodes struct {
|
||||
closer jsonrpc.ClientCloser
|
||||
}
|
||||
|
||||
func startNodesWithFunds(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
blocktime time.Duration,
|
||||
lookbackCap time.Duration,
|
||||
stateWaitLookbackLimit abi.ChainEpoch,
|
||||
) *testNodes {
|
||||
nodes := startNodes(ctx, t, blocktime, lookbackCap, stateWaitLookbackLimit)
|
||||
|
||||
// The full node starts with a wallet
|
||||
fullWalletAddr, err := nodes.full.WalletDefaultAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a wallet on the lite node
|
||||
liteWalletAddr, err := nodes.lite.WalletNew(ctx, types.KTSecp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send some funds from the full node to the lite node
|
||||
err = sendFunds(ctx, nodes.full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
||||
require.NoError(t, err)
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func startNodes(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
@ -302,3 +281,26 @@ func startNodes(
|
||||
|
||||
return &testNodes{lite: lite, full: full, miner: miner, closer: closer}
|
||||
}
|
||||
|
||||
func sendFunds(ctx context.Context, fromNode test.TestNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error {
|
||||
msg := &types.Message{
|
||||
From: fromAddr,
|
||||
To: toAddr,
|
||||
Value: amt,
|
||||
}
|
||||
|
||||
sm, err := fromNode.MpoolPushMessage(ctx, msg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := fromNode.StateWaitMsg(ctx, sm.Cid(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Receipt.ExitCode != 0 {
|
||||
return xerrors.Errorf("send funds failed with exit code %d", res.Receipt.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ type StateModuleAPI interface {
|
||||
StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error)
|
||||
StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error)
|
||||
StateNetworkVersion(ctx context.Context, key types.TipSetKey) (network.Version, error)
|
||||
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
||||
StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error)
|
||||
}
|
||||
|
||||
@ -1165,19 +1166,19 @@ func (a *StateAPI) StateVerifierStatus(ctx context.Context, addr address.Address
|
||||
// StateVerifiedClientStatus returns the data cap for the given address.
|
||||
// Returns zero if there is no entry in the data cap table for the
|
||||
// address.
|
||||
func (a *StateAPI) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) {
|
||||
act, err := a.StateGetActor(ctx, verifreg.Address, tsk)
|
||||
func (m *StateModule) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) {
|
||||
act, err := m.StateGetActor(ctx, verifreg.Address, tsk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aid, err := a.StateLookupID(ctx, addr, tsk)
|
||||
aid, err := m.StateLookupID(ctx, addr, tsk)
|
||||
if err != nil {
|
||||
log.Warnf("lookup failure %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vrs, err := verifreg.Load(a.StateManager.ChainStore().Store(ctx), act)
|
||||
vrs, err := verifreg.Load(m.StateManager.ChainStore().Store(ctx), act)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load verified registry state: %w", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user