package cli import ( "fmt" "path/filepath" "strconv" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/peer" "golang.org/x/xerrors" "gopkg.in/urfave/cli.v2" actors "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/types" ) var clientCmd = &cli.Command{ Name: "client", Usage: "Make deals, store data, retrieve data", Subcommands: []*cli.Command{ clientImportCmd, clientLocalCmd, clientDealCmd, clientFindCmd, clientRetrieveCmd, clientQueryAskCmd, }, } var clientImportCmd = &cli.Command{ Name: "import", Usage: "Import data", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) absPath, err := filepath.Abs(cctx.Args().First()) if err != nil { return err } c, err := api.ClientImport(ctx, absPath) if err != nil { return err } fmt.Println(c.String()) return nil }, } var clientLocalCmd = &cli.Command{ Name: "local", Usage: "List locally imported data", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) list, err := api.ClientListImports(ctx) if err != nil { return err } for _, v := range list { fmt.Printf("%s %s %d %s\n", v.Key, v.FilePath, v.Size, v.Status) } return nil }, } var clientDealCmd = &cli.Command{ Name: "deal", Usage: "Initialize storage deal with a miner", Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) if cctx.NArg() != 4 { return xerrors.New("expected 4 args: dataCid, miner, price, duration") } // [data, miner, dur] data, err := cid.Parse(cctx.Args().Get(0)) if err != nil { return err } miner, err := address.NewFromString(cctx.Args().Get(1)) if err != nil { return err } // TODO: parse bigint price, err := strconv.ParseInt(cctx.Args().Get(2), 10, 32) if err != nil { return err } dur, err := strconv.ParseInt(cctx.Args().Get(3), 10, 32) if err != nil { return err } proposal, err := api.ClientStartDeal(ctx, data, miner, types.NewInt(uint64(price)), uint64(dur)) if err != nil { return err } fmt.Println(proposal) return nil }, } var clientFindCmd = &cli.Command{ Name: "find", Usage: "find data in the network", Action: func(cctx *cli.Context) error { if !cctx.Args().Present() { fmt.Println("Usage: find [CID]") return nil } file, err := cid.Parse(cctx.Args().First()) if err != nil { return err } api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) // Check if we already have this data locally has, err := api.ClientHasLocal(ctx, file) if err != nil { return err } if has { fmt.Println("LOCAL") } offers, err := api.ClientFindData(ctx, file) if err != nil { return err } for _, offer := range offers { if offer.Err != "" { fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeerID, offer.Err) continue } fmt.Printf("RETRIEVAL %s@%s-%sfil-%db\n", offer.Miner, offer.MinerPeerID, offer.MinPrice, offer.Size) } return nil }, } var clientRetrieveCmd = &cli.Command{ Name: "retrieve", Usage: "retrieve data from network", Flags: []cli.Flag{ &cli.StringFlag{ Name: "address", Usage: "address to use for transactions", }, }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 2 { fmt.Println("Usage: retrieve [CID] [outfile]") return nil } api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) var payer address.Address if cctx.String("address") != "" { payer, err = address.NewFromString(cctx.String("address")) } else { payer, err = api.WalletDefaultAddress(ctx) } if err != nil { return err } file, err := cid.Parse(cctx.Args().Get(0)) if err != nil { return err } // Check if we already have this data locally /*has, err := api.ClientHasLocal(ctx, file) if err != nil { return err } if has { fmt.Println("Success: Already in local storage") return nil }*/ // TODO: fix offers, err := api.ClientFindData(ctx, file) if err != nil { return err } // TODO: parse offer strings from `client find`, make this smarter order := offers[0].Order() order.Client = payer err = api.ClientRetrieve(ctx, order, cctx.Args().Get(1)) if err == nil { fmt.Println("Success") } return err }, } var clientQueryAskCmd = &cli.Command{ Name: "query-ask", Usage: "find a miners ask", Flags: []cli.Flag{ &cli.StringFlag{ Name: "peerid", Usage: "specify peer ID of node to make query against", }, &cli.Int64Flag{ Name: "size", Usage: "data size in bytes", }, &cli.Int64Flag{ Name: "duration", Usage: "deal duration", }, }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { fmt.Println("Usage: query-ask [address]") return nil } maddr, err := address.NewFromString(cctx.Args().First()) if err != nil { return err } api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err } defer closer() ctx := ReqContext(cctx) var pid peer.ID if pidstr := cctx.String("peerid"); pidstr != "" { p, err := peer.IDFromString(pidstr) if err != nil { return err } pid = p } else { ret, err := api.StateCall(ctx, &types.Message{ To: maddr, From: maddr, Method: actors.MAMethods.GetPeerID, }, nil) if err != nil { return xerrors.Errorf("failed to get peerID for miner: %w", err) } if ret.ExitCode != 0 { return fmt.Errorf("call to GetPeerID was unsuccesful (exit code %d)", ret.ExitCode) } p, err := peer.IDFromBytes(ret.Return) if err != nil { return err } pid = p } ask, err := api.ClientQueryAsk(ctx, pid, maddr) if err != nil { return err } fmt.Printf("Ask: %s\n", maddr) fmt.Printf("Price per Byte: %s\n", ask.Ask.Price) size := cctx.Int64("size") if size == 0 { return nil } fmt.Printf("Price per Block: %s\n", types.BigMul(ask.Ask.Price, types.NewInt(uint64(size)))) duration := cctx.Int64("duration") if duration == 0 { return nil } fmt.Printf("Total Price: %s\n", types.BigMul(types.BigMul(ask.Ask.Price, types.NewInt(uint64(size))), types.NewInt(uint64(duration)))) return nil }, }