client cli: Interactive deal command
This commit is contained in:
parent
21d15d3151
commit
b6c9169a87
@ -227,6 +227,8 @@ type FullNode interface {
|
||||
ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error)
|
||||
// ClientGenCar generates a CAR file for the specified file.
|
||||
ClientGenCar(ctx context.Context, ref FileRef, outpath string) error
|
||||
// ClientDealSize calculates real deal data size
|
||||
ClientDealSize(ctx context.Context, root cid.Cid) (DataSize, error)
|
||||
|
||||
// ClientUnimport removes references to the specified file from filestore
|
||||
//ClientUnimport(path string)
|
||||
@ -666,6 +668,11 @@ type BlockTemplate struct {
|
||||
WinningPoStProof []abi.PoStProof
|
||||
}
|
||||
|
||||
type DataSize struct {
|
||||
PayloadSize int64
|
||||
PieceSize abi.PaddedPieceSize
|
||||
}
|
||||
|
||||
type CommPRet struct {
|
||||
Root cid.Cid
|
||||
Size abi.UnpaddedPieceSize
|
||||
|
@ -129,6 +129,7 @@ type FullNodeStruct struct {
|
||||
ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
|
||||
ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"`
|
||||
ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"`
|
||||
ClientDealSize func(ctx context.Context, root cid.Cid) (api.DataSize, error) `perm:"read"`
|
||||
|
||||
StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"`
|
||||
StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
|
||||
@ -418,6 +419,10 @@ func (c *FullNodeStruct) ClientGenCar(ctx context.Context, ref api.FileRef, outp
|
||||
return c.Internal.ClientGenCar(ctx, ref, outpath)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) {
|
||||
return c.Internal.ClientDealSize(ctx, root)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) GasEstimateGasPrice(ctx context.Context, nblocksincl uint64,
|
||||
sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) {
|
||||
return c.Internal.GasEstimateGasPrice(ctx, nblocksincl, sender, gaslimit, tsk)
|
||||
|
200
cli/client.go
200
cli/client.go
@ -3,13 +3,14 @@ package cli
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/fatih/color"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-cidutil/cidenc"
|
||||
@ -22,10 +23,12 @@ import (
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/go-multistore"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
@ -314,6 +317,10 @@ var clientDealCmd = &cli.Command{
|
||||
&CidBaseFlag,
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Args().Present() {
|
||||
return interactiveDeal(cctx)
|
||||
}
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -435,6 +442,197 @@ var clientDealCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func interactiveDeal(cctx *cli.Context) error {
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
state := "import"
|
||||
|
||||
var data cid.Cid
|
||||
var days int
|
||||
var maddr address.Address
|
||||
var ask storagemarket.StorageAsk
|
||||
var epochPrice big.Int
|
||||
var epochs abi.ChainEpoch
|
||||
|
||||
var a address.Address
|
||||
if from := cctx.String("from"); from != "" {
|
||||
faddr, err := address.NewFromString(from)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to parse 'from' address: %w", err)
|
||||
}
|
||||
a = faddr
|
||||
} else {
|
||||
def, err := api.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a = def
|
||||
}
|
||||
|
||||
printErr := func(err error) {
|
||||
fmt.Printf("%s %s\n", color.RedString("Error:"), err.Error())
|
||||
}
|
||||
|
||||
for {
|
||||
// TODO: better exit handling
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch state {
|
||||
case "import":
|
||||
fmt.Print("Data CID (from " + color.YellowString("lotus client import") + "): ")
|
||||
|
||||
var cidStr string
|
||||
_, err := fmt.Scan(&cidStr)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("reading cid string: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err = cid.Parse(cidStr)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("parsing cid string: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
state = "duration"
|
||||
case "duration":
|
||||
fmt.Print("Deal duration (days): ")
|
||||
|
||||
_, err := fmt.Scan(&days)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("parsing duration: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
state = "miner"
|
||||
case "miner":
|
||||
fmt.Print("Miner Address (t0..): ")
|
||||
var maddrStr string
|
||||
|
||||
_, err := fmt.Scan(&maddrStr)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("reading miner address: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
maddr, err = address.NewFromString(maddrStr)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("parsing miner address: %w", err))
|
||||
continue
|
||||
}
|
||||
|
||||
state = "query"
|
||||
case "query":
|
||||
color.Blue(".. querying miner ask")
|
||||
|
||||
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("failed to get peerID for miner: %w", err))
|
||||
state = "miner"
|
||||
continue
|
||||
}
|
||||
|
||||
a, err := api.ClientQueryAsk(ctx, mi.PeerId, maddr)
|
||||
if err != nil {
|
||||
printErr(xerrors.Errorf("failed to query ask: %w", err))
|
||||
state = "miner"
|
||||
continue
|
||||
}
|
||||
|
||||
ask = *a.Ask
|
||||
|
||||
// TODO: run more validation
|
||||
state = "confirm"
|
||||
case "confirm":
|
||||
fromBal, err := api.WalletBalance(ctx, a)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("checking from address balance: %w", err)
|
||||
}
|
||||
|
||||
color.Blue(".. calculating data size\n")
|
||||
ds, err := api.ClientDealSize(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dur := 24 * time.Hour * time.Duration(days)
|
||||
|
||||
epochs = abi.ChainEpoch(dur / (time.Duration(build.BlockDelaySecs) * time.Second))
|
||||
// TODO: do some more or epochs math (round to miner PP, deal start buffer)
|
||||
|
||||
gib := types.NewInt(1 << 30)
|
||||
|
||||
// TODO: price is based on PaddedPieceSize, right?
|
||||
epochPrice = types.BigDiv(types.BigMul(ask.Price, types.NewInt(uint64(ds.PieceSize))), gib)
|
||||
totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs)))
|
||||
|
||||
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)
|
||||
fmt.Printf("Total price: ~%s (%s per epoch)\n", types.FIL(totalPrice), types.FIL(epochPrice))
|
||||
|
||||
state = "accept"
|
||||
case "accept":
|
||||
fmt.Print("\nAccept (yes/no): ")
|
||||
|
||||
var yn string
|
||||
_, err := fmt.Scan(&yn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if yn == "no" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if yn != "yes" {
|
||||
fmt.Println("Type in full 'yes' or 'no'")
|
||||
continue
|
||||
}
|
||||
|
||||
state = "execute"
|
||||
case "execute":
|
||||
color.Blue(".. executing")
|
||||
proposal, err := api.ClientStartDeal(ctx, &lapi.StartDealParams{
|
||||
Data: &storagemarket.DataRef{
|
||||
TransferType: storagemarket.TTGraphsync,
|
||||
Root: data,
|
||||
},
|
||||
Wallet: a,
|
||||
Miner: maddr,
|
||||
EpochPrice: epochPrice,
|
||||
MinBlocksDuration: uint64(epochs),
|
||||
DealStartEpoch: abi.ChainEpoch(cctx.Int64("start-epoch")),
|
||||
FastRetrieval: cctx.Bool("fast-retrieval"),
|
||||
VerifiedDeal: false, // TODO: Allow setting
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoder, err := GetCidEncoder(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\nDeal CID:", color.GreenString(encoder.Encode(*proposal)))
|
||||
return nil
|
||||
default:
|
||||
return xerrors.Errorf("unknown state: %s", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clientFindCmd = &cli.Command{
|
||||
Name: "find",
|
||||
Usage: "Find data in the network",
|
||||
|
1
go.mod
1
go.mod
@ -28,6 +28,7 @@ require (
|
||||
github.com/filecoin-project/go-fil-markets v0.5.3-0.20200730194453-26fac2c92927
|
||||
github.com/filecoin-project/go-jsonrpc v0.1.1-0.20200602181149-522144ab4e24
|
||||
github.com/filecoin-project/go-multistore v0.0.2
|
||||
github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261
|
||||
github.com/filecoin-project/go-statestore v0.1.0
|
||||
github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/filecoin-project/go-fil-markets/shared"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/go-multistore"
|
||||
"github.com/filecoin-project/go-padreader"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
@ -69,7 +70,7 @@ type API struct {
|
||||
|
||||
Imports dtypes.ClientImportMgr
|
||||
|
||||
RetBstore dtypes.ClientBlockstore // TODO: try to remove
|
||||
CombinedBstore dtypes.ClientBlockstore // TODO: try to remove
|
||||
}
|
||||
|
||||
func calcDealExpiration(minDuration uint64, md *miner.DeadlineInfo, startEpoch abi.ChainEpoch) abi.ChainEpoch {
|
||||
@ -554,6 +555,31 @@ func (a *API) ClientCalcCommP(ctx context.Context, inpath string, miner address.
|
||||
}, nil
|
||||
}
|
||||
|
||||
type lenWriter int64
|
||||
|
||||
func (w *lenWriter) Write(p []byte) (n int, err error) {
|
||||
*w += lenWriter(len(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (a *API) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) {
|
||||
dag := merkledag.NewDAGService(blockservice.New(a.CombinedBstore, offline.Exchange(a.CombinedBstore)))
|
||||
|
||||
w := lenWriter(0)
|
||||
|
||||
err := car.WriteCar(ctx, dag, []cid.Cid{root}, &w)
|
||||
if err != nil {
|
||||
return api.DataSize{}, err
|
||||
}
|
||||
|
||||
up := padreader.PaddedSize(uint64(w))
|
||||
|
||||
return api.DataSize{
|
||||
PayloadSize: int64(w),
|
||||
PieceSize: up.Padded(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *API) ClientGenCar(ctx context.Context, ref api.FileRef, outputPath string) error {
|
||||
id, st, err := a.imgr().NewStore()
|
||||
if err != nil {
|
||||
|
@ -2,7 +2,6 @@ package modules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket/impl/funds"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-multistore"
|
||||
@ -19,6 +18,7 @@ import (
|
||||
rmnet "github.com/filecoin-project/go-fil-markets/retrievalmarket/network"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
storageimpl "github.com/filecoin-project/go-fil-markets/storagemarket/impl"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket/impl/funds"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation"
|
||||
smnet "github.com/filecoin-project/go-fil-markets/storagemarket/network"
|
||||
"github.com/filecoin-project/go-statestore"
|
||||
@ -64,7 +64,7 @@ func ClientImportMgr(mds dtypes.ClientMultiDstore, ds dtypes.MetadataDS) dtypes.
|
||||
|
||||
func ClientBlockstore(imgr dtypes.ClientImportMgr) dtypes.ClientBlockstore {
|
||||
// in most cases this is now unused in normal operations -- however, it's important to preserve for the IPFS use case
|
||||
return blockstore.NewTemporary()
|
||||
return blockstore.WrapIDStore(imgr.Blockstore)
|
||||
}
|
||||
|
||||
// RegisterClientValidator is an initialization hook that registers the client
|
||||
|
Loading…
Reference in New Issue
Block a user