From 0177309dcf4260a8578ee9b397d42d187f64a4d4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 6 Jun 2018 20:44:41 -0700 Subject: [PATCH] cmd/lncli: modify sendtoroute cmd to make reading routes from stdin optional In this commit we modify the existing sendtoroutes command such that users can either specify the route over stdin (using the special '-' flag), via the keyword argument, or via the positional argument. --- cmd/lncli/commands.go | 111 ++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 8e8f24ad..482b4a21 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -1809,31 +1809,43 @@ var sendToRouteCommand = cli.Command{ Usage: "send a payment over a predefined route", Description: ` Send a payment over Lightning using a specific route. One must specify - a list of routes to attempt and the payment hash. + a list of routes to attempt and the payment hash. This command can even + be chained with the response to queryroutes. This command can be used + to implement channel rebalancing by crafting a self-route, or even + atomic swaps using a self-route that crosses multiple chains. - The --debug_send flag is provided for usage *purely* in test - environments. If specified, then the payment hash isn't required, as - it'll use the hash of all zeroes. This mode allows one to quickly test - payment connectivity without having to create an invoice at the - destination. + There are three ways to specify routes: + * using the --routes parameter to manually specify a JSON encoded + set of routes in the format of the return value of queryroutes: + (lncli sendtoroute --payment_hash= --routes=) + + * passing the routes as a positional argument: + (lncli sendtoroute --payment_hash=pay_hash ) + + * or reading in the routes from stdin, which can allow chaining the + response from queryroutes, or even read in a file with a set of + pre-computed routes: + (lncli queryroutes --args.. | lncli sendtoroute --payment_hash= - + + notice the '-' at the end, which signals that lncli should read + the route in from stdin `, - ArgsUsage: "(lncli queryroutes --dest= --amt= " + - "| lncli sendtoroute --payment_hash=)", Flags: []cli.Flag{ cli.StringFlag{ - Name: "payment_hash, r", + Name: "payment_hash, pay_hash", Usage: "the hash to use within the payment's HTLC", }, - cli.BoolFlag{ - Name: "debug_send", - Usage: "use the debug rHash when sending the HTLC", + cli.StringFlag{ + Name: "routes, r", + Usage: "a json array string in the format of the response " + + "of queryroutes that denotes which routes to use", }, }, Action: sendToRoute, } func sendToRoute(ctx *cli.Context) error { - // Show command help if no arguments provieded + // Show command help if no arguments provided. if ctx.NArg() == 0 && ctx.NumFlags() == 0 { cli.ShowCommandHelp(ctx, "sendtoroute") return nil @@ -1841,48 +1853,65 @@ func sendToRoute(ctx *cli.Context) error { args := ctx.Args() - b, err := ioutil.ReadAll(os.Stdin) + var ( + rHash []byte + err error + ) + switch { + case ctx.IsSet("payment_hash"): + rHash, err = hex.DecodeString(ctx.String("payment_hash")) + case args.Present(): + rHash, err = hex.DecodeString(args.First()) + + args = args.Tail() + default: + return fmt.Errorf("payment hash argument missing") + } + if err != nil { return err } - if len(b) == 0 { - return fmt.Errorf("queryroutes output is empty") + + if len(rHash) != 32 { + return fmt.Errorf("payment hash must be exactly 32 "+ + "bytes, is instead %d", len(rHash)) } - qroutes := &lnrpc.QueryRoutesResponse{} - err = jsonpb.UnmarshalString(string(b), qroutes) - if err != nil { - return fmt.Errorf("unable to unmarshal json string from "+ - "incoming array of routes: %v", err) - } + var jsonRoutes string + switch { + // The user is specifying the routes explicitly via the key word + // argument. + case ctx.IsSet("routes"): + jsonRoutes = ctx.String("routes") - var rHash []byte - if ctx.Bool("debug_send") && - (ctx.IsSet("payment_hash") || args.Present()) { - return fmt.Errorf("do not provide a payment hash with debug send") - } else if !ctx.Bool("debug_send") { - switch { - case ctx.IsSet("payment_hash"): - rHash, err = hex.DecodeString(ctx.String("payment_hash")) - case args.Present(): - rHash, err = hex.DecodeString(args.First()) - default: - return fmt.Errorf("payment hash argument missing") - } + // The user is specifying the routes as a positional argument. + case args.Present() && args.First() != "-": + jsonRoutes = args.First() + // The user is signalling that we should read stdin in order to parse + // the set of target routes. + case args.Present() && args.First() == "-": + b, err := ioutil.ReadAll(os.Stdin) if err != nil { return err } - - if len(rHash) != 32 { - return fmt.Errorf("payment hash must be exactly 32 "+ - "bytes, is instead %d", len(rHash)) + if len(b) == 0 { + return fmt.Errorf("queryroutes output is empty") } + + jsonRoutes = string(b) + } + + routes := &lnrpc.QueryRoutesResponse{} + err = jsonpb.UnmarshalString(jsonRoutes, routes) + if err != nil { + return fmt.Errorf("unable to unmarshal json string "+ + "from incoming array of routes: %v", err) } req := &lnrpc.SendToRouteRequest{ PaymentHash: rHash, - Routes: qroutes.Routes, + Routes: routes.Routes, } return sendToRouteRequest(ctx, req) @@ -1906,8 +1935,6 @@ func sendToRouteRequest(ctx *cli.Context, req *lnrpc.SendToRouteRequest) error { return err } - paymentStream.CloseSend() - printJSON(struct { E string `json:"payment_error"` P string `json:"payment_preimage"`