From da418ef46b54bbe2da5b598cce706c5e865f7263 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 7 Sep 2021 13:08:47 +0200 Subject: [PATCH 1/5] cmd/lncli: move all payment related cmds to single file With this commit we move all commands that can be found within the "Payments" category into its own file called cmd_payments.go. The only exception are the Mission Control related commands which we are going to move to their own category and file in the next commit. --- cmd/lncli/cmd_build_route.go | 91 ----- cmd/lncli/{cmd_pay.go => cmd_payments.go} | 407 ++++++++++++++++++++++ cmd/lncli/commands.go | 328 ----------------- 3 files changed, 407 insertions(+), 419 deletions(-) delete mode 100644 cmd/lncli/cmd_build_route.go rename cmd/lncli/{cmd_pay.go => cmd_payments.go} (70%) diff --git a/cmd/lncli/cmd_build_route.go b/cmd/lncli/cmd_build_route.go deleted file mode 100644 index 81129b4d19..0000000000 --- a/cmd/lncli/cmd_build_route.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "strings" - - "github.com/lightningnetwork/lnd/chainreg" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" -) - -var buildRouteCommand = cli.Command{ - Name: "buildroute", - Category: "Payments", - Usage: "Build a route from a list of hop pubkeys.", - Action: actionDecorator(buildRoute), - Flags: []cli.Flag{ - cli.Int64Flag{ - Name: "amt", - Usage: "the amount to send expressed in satoshis. If" + - "not set, the minimum routable amount is used", - }, - cli.Int64Flag{ - Name: "final_cltv_delta", - Usage: "number of blocks the last hop has to reveal " + - "the preimage", - Value: chainreg.DefaultBitcoinTimeLockDelta, - }, - cli.StringFlag{ - Name: "hops", - Usage: "comma separated hex pubkeys", - }, - cli.Uint64Flag{ - Name: "outgoing_chan_id", - Usage: "short channel id of the outgoing channel to " + - "use for the first hop of the payment", - Value: 0, - }, - }, -} - -func buildRoute(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - if !ctx.IsSet("hops") { - return errors.New("hops required") - } - - // Build list of hop addresses for the rpc. - hops := strings.Split(ctx.String("hops"), ",") - rpcHops := make([][]byte, 0, len(hops)) - for _, k := range hops { - pubkey, err := route.NewVertexFromStr(k) - if err != nil { - return fmt.Errorf("error parsing %v: %v", k, err) - } - rpcHops = append(rpcHops, pubkey[:]) - } - - var amtMsat int64 - hasAmt := ctx.IsSet("amt") - if hasAmt { - amtMsat = ctx.Int64("amt") * 1000 - if amtMsat == 0 { - return fmt.Errorf("non-zero amount required") - } - } - - // Call BuildRoute rpc. - req := &routerrpc.BuildRouteRequest{ - AmtMsat: amtMsat, - FinalCltvDelta: int32(ctx.Int64("final_cltv_delta")), - HopPubkeys: rpcHops, - OutgoingChanId: ctx.Uint64("outgoing_chan_id"), - } - - route, err := client.BuildRoute(ctxc, req) - if err != nil { - return err - } - - printRespJSON(route) - - return nil -} diff --git a/cmd/lncli/cmd_pay.go b/cmd/lncli/cmd_payments.go similarity index 70% rename from cmd/lncli/cmd_pay.go rename to cmd/lncli/cmd_payments.go index dcb7d96bef..f6ee04b2bd 100644 --- a/cmd/lncli/cmd_pay.go +++ b/cmd/lncli/cmd_payments.go @@ -18,6 +18,7 @@ import ( "github.com/jedib0t/go-pretty/table" "github.com/jedib0t/go-pretty/text" "github.com/lightninglabs/protobuf-hex-display/jsonpb" + "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -987,6 +988,412 @@ func sendToRouteRequest(ctx *cli.Context, req *routerrpc.SendToRouteRequest) err return nil } +var queryRoutesCommand = cli.Command{ + Name: "queryroutes", + Category: "Payments", + Usage: "Query a route to a destination.", + Description: "Queries the channel router for a potential path to the destination that has sufficient flow for the amount including fees", + ArgsUsage: "dest amt", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "dest", + Usage: "the 33-byte hex-encoded public key for the payment " + + "destination", + }, + cli.Int64Flag{ + Name: "amt", + Usage: "the amount to send expressed in satoshis", + }, + cli.Int64Flag{ + Name: "fee_limit", + Usage: "maximum fee allowed in satoshis when sending " + + "the payment", + }, + cli.Int64Flag{ + Name: "fee_limit_percent", + Usage: "percentage of the payment's amount used as the " + + "maximum fee allowed when sending the payment", + }, + cli.Int64Flag{ + Name: "final_cltv_delta", + Usage: "(optional) number of blocks the last hop has to reveal " + + "the preimage", + }, + cli.BoolFlag{ + Name: "use_mc", + Usage: "use mission control probabilities", + }, + cli.Uint64Flag{ + Name: "outgoing_chanid", + Usage: "(optional) the channel id of the channel " + + "that must be taken to the first hop", + }, + cltvLimitFlag, + }, + Action: actionDecorator(queryRoutes), +} + +func queryRoutes(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getClient(ctx) + defer cleanUp() + + var ( + dest string + amt int64 + err error + ) + + args := ctx.Args() + + switch { + case ctx.IsSet("dest"): + dest = ctx.String("dest") + case args.Present(): + dest = args.First() + args = args.Tail() + default: + return fmt.Errorf("dest argument missing") + } + + switch { + case ctx.IsSet("amt"): + amt = ctx.Int64("amt") + case args.Present(): + amt, err = strconv.ParseInt(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode amt argument: %v", err) + } + default: + return fmt.Errorf("amt argument missing") + } + + feeLimit, err := retrieveFeeLimitLegacy(ctx) + if err != nil { + return err + } + + req := &lnrpc.QueryRoutesRequest{ + PubKey: dest, + Amt: amt, + FeeLimit: feeLimit, + FinalCltvDelta: int32(ctx.Int("final_cltv_delta")), + UseMissionControl: ctx.Bool("use_mc"), + CltvLimit: uint32(ctx.Uint64(cltvLimitFlag.Name)), + OutgoingChanId: ctx.Uint64("outgoing_chanid"), + } + + route, err := client.QueryRoutes(ctxc, req) + if err != nil { + return err + } + + printRespJSON(route) + return nil +} + +// retrieveFeeLimitLegacy retrieves the fee limit based on the different fee +// limit flags passed. This function will eventually disappear in favor of +// retrieveFeeLimit and the new payment rpc. +func retrieveFeeLimitLegacy(ctx *cli.Context) (*lnrpc.FeeLimit, error) { + switch { + case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"): + return nil, fmt.Errorf("either fee_limit or fee_limit_percent " + + "can be set, but not both") + case ctx.IsSet("fee_limit"): + return &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Fixed{ + Fixed: ctx.Int64("fee_limit"), + }, + }, nil + case ctx.IsSet("fee_limit_percent"): + feeLimitPercent := ctx.Int64("fee_limit_percent") + if feeLimitPercent < 0 { + return nil, errors.New("negative fee limit percentage " + + "provided") + } + return &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Percent{ + Percent: feeLimitPercent, + }, + }, nil + } + + // Since the fee limit flags aren't required, we don't return an error + // if they're not set. + return nil, nil +} + +var listPaymentsCommand = cli.Command{ + Name: "listpayments", + Category: "Payments", + Usage: "List all outgoing payments.", + Description: "This command enables the retrieval of payments stored " + + "in the database. Pagination is supported by the usage of " + + "index_offset in combination with the paginate_forwards flag. " + + "Reversed pagination is enabled by default to receive " + + "current payments first. Pagination can be resumed by using " + + "the returned last_index_offset (for forwards order), or " + + "first_index_offset (for reversed order) as the offset_index. ", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "include_incomplete", + Usage: "if set to true, payments still in flight (or " + + "failed) will be returned as well, keeping" + + "indices for payments the same as without " + + "the flag", + }, + cli.UintFlag{ + Name: "index_offset", + Usage: "The index of a payment that will be used as " + + "either the start (in forwards mode) or end " + + "(in reverse mode) of a query to determine " + + "which payments should be returned in the " + + "response, where the index_offset is " + + "excluded. If index_offset is set to zero in " + + "reversed mode, the query will end with the " + + "last payment made.", + }, + cli.UintFlag{ + Name: "max_payments", + Usage: "the max number of payments to return, by " + + "default, all completed payments are returned", + }, + cli.BoolFlag{ + Name: "paginate_forwards", + Usage: "if set, payments succeeding the " + + "index_offset will be returned, allowing " + + "forwards pagination", + }, + }, + Action: actionDecorator(listPayments), +} + +func listPayments(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getClient(ctx) + defer cleanUp() + + req := &lnrpc.ListPaymentsRequest{ + IncludeIncomplete: ctx.Bool("include_incomplete"), + IndexOffset: uint64(ctx.Uint("index_offset")), + MaxPayments: uint64(ctx.Uint("max_payments")), + Reversed: !ctx.Bool("paginate_forwards"), + } + + payments, err := client.ListPayments(ctxc, req) + if err != nil { + return err + } + + printRespJSON(payments) + return nil +} + +var forwardingHistoryCommand = cli.Command{ + Name: "fwdinghistory", + Category: "Payments", + Usage: "Query the history of all forwarded HTLCs.", + ArgsUsage: "start_time [end_time] [index_offset] [max_events]", + Description: ` + Query the HTLC switch's internal forwarding log for all completed + payment circuits (HTLCs) over a particular time range (--start_time and + --end_time). The start and end times are meant to be expressed in + seconds since the Unix epoch. + Alternatively negative time ranges can be used, e.g. "-3d". Supports + s(seconds), m(minutes), h(ours), d(ays), w(eeks), M(onths), y(ears). + Month equals 30.44 days, year equals 365.25 days. + If --start_time isn't provided, then 24 hours ago is used. If + --end_time isn't provided, then the current time is used. + + The max number of events returned is 50k. The default number is 100, + callers can use the --max_events param to modify this value. + + Finally, callers can skip a series of events using the --index_offset + parameter. Each response will contain the offset index of the last + entry. Using this callers can manually paginate within a time slice. + `, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "start_time", + Usage: "the starting time for the query " + + `as unix timestamp or relative e.g. "-1w"`, + }, + cli.StringFlag{ + Name: "end_time", + Usage: "the end time for the query " + + `as unix timestamp or relative e.g. "-1w"`, + }, + cli.Int64Flag{ + Name: "index_offset", + Usage: "the number of events to skip", + }, + cli.Int64Flag{ + Name: "max_events", + Usage: "the max number of events to return", + }, + }, + Action: actionDecorator(forwardingHistory), +} + +func forwardingHistory(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getClient(ctx) + defer cleanUp() + + var ( + startTime, endTime uint64 + indexOffset, maxEvents uint32 + err error + ) + args := ctx.Args() + now := time.Now() + + switch { + case ctx.IsSet("start_time"): + startTime, err = parseTime(ctx.String("start_time"), now) + case args.Present(): + startTime, err = parseTime(args.First(), now) + args = args.Tail() + default: + now := time.Now() + startTime = uint64(now.Add(-time.Hour * 24).Unix()) + } + if err != nil { + return fmt.Errorf("unable to decode start_time: %v", err) + } + + switch { + case ctx.IsSet("end_time"): + endTime, err = parseTime(ctx.String("end_time"), now) + case args.Present(): + endTime, err = parseTime(args.First(), now) + args = args.Tail() + default: + endTime = uint64(now.Unix()) + } + if err != nil { + return fmt.Errorf("unable to decode end_time: %v", err) + } + + switch { + case ctx.IsSet("index_offset"): + indexOffset = uint32(ctx.Int64("index_offset")) + case args.Present(): + i, err := strconv.ParseInt(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode index_offset: %v", err) + } + indexOffset = uint32(i) + args = args.Tail() + } + + switch { + case ctx.IsSet("max_events"): + maxEvents = uint32(ctx.Int64("max_events")) + case args.Present(): + m, err := strconv.ParseInt(args.First(), 10, 64) + if err != nil { + return fmt.Errorf("unable to decode max_events: %v", err) + } + maxEvents = uint32(m) + args = args.Tail() + } + + req := &lnrpc.ForwardingHistoryRequest{ + StartTime: startTime, + EndTime: endTime, + IndexOffset: indexOffset, + NumMaxEvents: maxEvents, + } + resp, err := client.ForwardingHistory(ctxc, req) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} + +var buildRouteCommand = cli.Command{ + Name: "buildroute", + Category: "Payments", + Usage: "Build a route from a list of hop pubkeys.", + Action: actionDecorator(buildRoute), + Flags: []cli.Flag{ + cli.Int64Flag{ + Name: "amt", + Usage: "the amount to send expressed in satoshis. If" + + "not set, the minimum routable amount is used", + }, + cli.Int64Flag{ + Name: "final_cltv_delta", + Usage: "number of blocks the last hop has to reveal " + + "the preimage", + Value: chainreg.DefaultBitcoinTimeLockDelta, + }, + cli.StringFlag{ + Name: "hops", + Usage: "comma separated hex pubkeys", + }, + cli.Uint64Flag{ + Name: "outgoing_chan_id", + Usage: "short channel id of the outgoing channel to " + + "use for the first hop of the payment", + Value: 0, + }, + }, +} + +func buildRoute(ctx *cli.Context) error { + ctxc := getContext() + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + if !ctx.IsSet("hops") { + return errors.New("hops required") + } + + // Build list of hop addresses for the rpc. + hops := strings.Split(ctx.String("hops"), ",") + rpcHops := make([][]byte, 0, len(hops)) + for _, k := range hops { + pubkey, err := route.NewVertexFromStr(k) + if err != nil { + return fmt.Errorf("error parsing %v: %v", k, err) + } + rpcHops = append(rpcHops, pubkey[:]) + } + + var amtMsat int64 + hasAmt := ctx.IsSet("amt") + if hasAmt { + amtMsat = ctx.Int64("amt") * 1000 + if amtMsat == 0 { + return fmt.Errorf("non-zero amount required") + } + } + + // Call BuildRoute rpc. + req := &routerrpc.BuildRouteRequest{ + AmtMsat: amtMsat, + FinalCltvDelta: int32(ctx.Int64("final_cltv_delta")), + HopPubkeys: rpcHops, + OutgoingChanId: ctx.Uint64("outgoing_chan_id"), + } + + route, err := client.BuildRoute(ctxc, req) + if err != nil { + return err + } + + printRespJSON(route) + + return nil +} + // ESC is the ASCII code for escape character const ESC = 27 diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index fa3a1a8e96..68545d2c50 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -14,7 +14,6 @@ import ( "strconv" "strings" "sync" - "time" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -1550,72 +1549,6 @@ func getNodeMetrics(ctx *cli.Context) error { return nil } -var listPaymentsCommand = cli.Command{ - Name: "listpayments", - Category: "Payments", - Usage: "List all outgoing payments.", - Description: "This command enables the retrieval of payments stored " + - "in the database. Pagination is supported by the usage of " + - "index_offset in combination with the paginate_forwards flag. " + - "Reversed pagination is enabled by default to receive " + - "current payments first. Pagination can be resumed by using " + - "the returned last_index_offset (for forwards order), or " + - "first_index_offset (for reversed order) as the offset_index. ", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "include_incomplete", - Usage: "if set to true, payments still in flight (or " + - "failed) will be returned as well, keeping" + - "indices for payments the same as without " + - "the flag", - }, - cli.UintFlag{ - Name: "index_offset", - Usage: "The index of a payment that will be used as " + - "either the start (in forwards mode) or end " + - "(in reverse mode) of a query to determine " + - "which payments should be returned in the " + - "response, where the index_offset is " + - "excluded. If index_offset is set to zero in " + - "reversed mode, the query will end with the " + - "last payment made.", - }, - cli.UintFlag{ - Name: "max_payments", - Usage: "the max number of payments to return, by " + - "default, all completed payments are returned", - }, - cli.BoolFlag{ - Name: "paginate_forwards", - Usage: "if set, payments succeeding the " + - "index_offset will be returned, allowing " + - "forwards pagination", - }, - }, - Action: actionDecorator(listPayments), -} - -func listPayments(ctx *cli.Context) error { - ctxc := getContext() - client, cleanUp := getClient(ctx) - defer cleanUp() - - req := &lnrpc.ListPaymentsRequest{ - IncludeIncomplete: ctx.Bool("include_incomplete"), - IndexOffset: uint64(ctx.Uint("index_offset")), - MaxPayments: uint64(ctx.Uint("max_payments")), - Reversed: !ctx.Bool("paginate_forwards"), - } - - payments, err := client.ListPayments(ctxc, req) - if err != nil { - return err - } - - printRespJSON(payments) - return nil -} - var getChanInfoCommand = cli.Command{ Name: "getchaninfo", Category: "Graph", @@ -1719,142 +1652,6 @@ func getNodeInfo(ctx *cli.Context) error { return nil } -var queryRoutesCommand = cli.Command{ - Name: "queryroutes", - Category: "Payments", - Usage: "Query a route to a destination.", - Description: "Queries the channel router for a potential path to the destination that has sufficient flow for the amount including fees", - ArgsUsage: "dest amt", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "dest", - Usage: "the 33-byte hex-encoded public key for the payment " + - "destination", - }, - cli.Int64Flag{ - Name: "amt", - Usage: "the amount to send expressed in satoshis", - }, - cli.Int64Flag{ - Name: "fee_limit", - Usage: "maximum fee allowed in satoshis when sending " + - "the payment", - }, - cli.Int64Flag{ - Name: "fee_limit_percent", - Usage: "percentage of the payment's amount used as the " + - "maximum fee allowed when sending the payment", - }, - cli.Int64Flag{ - Name: "final_cltv_delta", - Usage: "(optional) number of blocks the last hop has to reveal " + - "the preimage", - }, - cli.BoolFlag{ - Name: "use_mc", - Usage: "use mission control probabilities", - }, - cli.Uint64Flag{ - Name: "outgoing_chanid", - Usage: "(optional) the channel id of the channel " + - "that must be taken to the first hop", - }, - cltvLimitFlag, - }, - Action: actionDecorator(queryRoutes), -} - -func queryRoutes(ctx *cli.Context) error { - ctxc := getContext() - client, cleanUp := getClient(ctx) - defer cleanUp() - - var ( - dest string - amt int64 - err error - ) - - args := ctx.Args() - - switch { - case ctx.IsSet("dest"): - dest = ctx.String("dest") - case args.Present(): - dest = args.First() - args = args.Tail() - default: - return fmt.Errorf("dest argument missing") - } - - switch { - case ctx.IsSet("amt"): - amt = ctx.Int64("amt") - case args.Present(): - amt, err = strconv.ParseInt(args.First(), 10, 64) - if err != nil { - return fmt.Errorf("unable to decode amt argument: %v", err) - } - default: - return fmt.Errorf("amt argument missing") - } - - feeLimit, err := retrieveFeeLimitLegacy(ctx) - if err != nil { - return err - } - - req := &lnrpc.QueryRoutesRequest{ - PubKey: dest, - Amt: amt, - FeeLimit: feeLimit, - FinalCltvDelta: int32(ctx.Int("final_cltv_delta")), - UseMissionControl: ctx.Bool("use_mc"), - CltvLimit: uint32(ctx.Uint64(cltvLimitFlag.Name)), - OutgoingChanId: ctx.Uint64("outgoing_chanid"), - } - - route, err := client.QueryRoutes(ctxc, req) - if err != nil { - return err - } - - printRespJSON(route) - return nil -} - -// retrieveFeeLimitLegacy retrieves the fee limit based on the different fee -// limit flags passed. This function will eventually disappear in favor of -// retrieveFeeLimit and the new payment rpc. -func retrieveFeeLimitLegacy(ctx *cli.Context) (*lnrpc.FeeLimit, error) { - switch { - case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"): - return nil, fmt.Errorf("either fee_limit or fee_limit_percent " + - "can be set, but not both") - case ctx.IsSet("fee_limit"): - return &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Fixed{ - Fixed: ctx.Int64("fee_limit"), - }, - }, nil - case ctx.IsSet("fee_limit_percent"): - feeLimitPercent := ctx.Int64("fee_limit_percent") - if feeLimitPercent < 0 { - return nil, errors.New("negative fee limit percentage " + - "provided") - } - return &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Percent{ - Percent: feeLimitPercent, - }, - }, nil - } - - // Since the fee limit flags aren't required, we don't return an error - // if they're not set. - return nil, nil -} - var getNetworkInfoCommand = cli.Command{ Name: "getnetworkinfo", Category: "Channels", @@ -2330,131 +2127,6 @@ func updateChannelPolicy(ctx *cli.Context) error { return nil } -var forwardingHistoryCommand = cli.Command{ - Name: "fwdinghistory", - Category: "Payments", - Usage: "Query the history of all forwarded HTLCs.", - ArgsUsage: "start_time [end_time] [index_offset] [max_events]", - Description: ` - Query the HTLC switch's internal forwarding log for all completed - payment circuits (HTLCs) over a particular time range (--start_time and - --end_time). The start and end times are meant to be expressed in - seconds since the Unix epoch. - Alternatively negative time ranges can be used, e.g. "-3d". Supports - s(seconds), m(minutes), h(ours), d(ays), w(eeks), M(onths), y(ears). - Month equals 30.44 days, year equals 365.25 days. - If --start_time isn't provided, then 24 hours ago is used. If - --end_time isn't provided, then the current time is used. - - The max number of events returned is 50k. The default number is 100, - callers can use the --max_events param to modify this value. - - Finally, callers can skip a series of events using the --index_offset - parameter. Each response will contain the offset index of the last - entry. Using this callers can manually paginate within a time slice. - `, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "start_time", - Usage: "the starting time for the query " + - `as unix timestamp or relative e.g. "-1w"`, - }, - cli.StringFlag{ - Name: "end_time", - Usage: "the end time for the query " + - `as unix timestamp or relative e.g. "-1w"`, - }, - cli.Int64Flag{ - Name: "index_offset", - Usage: "the number of events to skip", - }, - cli.Int64Flag{ - Name: "max_events", - Usage: "the max number of events to return", - }, - }, - Action: actionDecorator(forwardingHistory), -} - -func forwardingHistory(ctx *cli.Context) error { - ctxc := getContext() - client, cleanUp := getClient(ctx) - defer cleanUp() - - var ( - startTime, endTime uint64 - indexOffset, maxEvents uint32 - err error - ) - args := ctx.Args() - now := time.Now() - - switch { - case ctx.IsSet("start_time"): - startTime, err = parseTime(ctx.String("start_time"), now) - case args.Present(): - startTime, err = parseTime(args.First(), now) - args = args.Tail() - default: - now := time.Now() - startTime = uint64(now.Add(-time.Hour * 24).Unix()) - } - if err != nil { - return fmt.Errorf("unable to decode start_time: %v", err) - } - - switch { - case ctx.IsSet("end_time"): - endTime, err = parseTime(ctx.String("end_time"), now) - case args.Present(): - endTime, err = parseTime(args.First(), now) - args = args.Tail() - default: - endTime = uint64(now.Unix()) - } - if err != nil { - return fmt.Errorf("unable to decode end_time: %v", err) - } - - switch { - case ctx.IsSet("index_offset"): - indexOffset = uint32(ctx.Int64("index_offset")) - case args.Present(): - i, err := strconv.ParseInt(args.First(), 10, 64) - if err != nil { - return fmt.Errorf("unable to decode index_offset: %v", err) - } - indexOffset = uint32(i) - args = args.Tail() - } - - switch { - case ctx.IsSet("max_events"): - maxEvents = uint32(ctx.Int64("max_events")) - case args.Present(): - m, err := strconv.ParseInt(args.First(), 10, 64) - if err != nil { - return fmt.Errorf("unable to decode max_events: %v", err) - } - maxEvents = uint32(m) - args = args.Tail() - } - - req := &lnrpc.ForwardingHistoryRequest{ - StartTime: startTime, - EndTime: endTime, - IndexOffset: indexOffset, - NumMaxEvents: maxEvents, - } - resp, err := client.ForwardingHistory(ctxc, req) - if err != nil { - return err - } - - printRespJSON(resp) - return nil -} - var exportChanBackupCommand = cli.Command{ Name: "exportchanbackup", Category: "Channels", From 28c25d7e9cb6811d01cad7b55d8794578997a245 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 7 Sep 2021 13:11:12 +0200 Subject: [PATCH 2/5] cmd/lncli: group Mission Control cmds into own category To make the "Payments" category a bit less overloaded and to move the Mission Control configuration commands away from the "root" category, we create the new "Mission Control" category that we move the commands into. We do this in a single commit so the next one where we move them into the same file can be a pure code move without any additional changes. --- cmd/lncli/cmd_mc_cfg_get.go | 5 +++-- cmd/lncli/cmd_mc_cfg_set.go | 5 +++-- cmd/lncli/cmd_query_mission_control.go | 2 +- cmd/lncli/cmd_query_probability.go | 2 +- cmd/lncli/cmd_reset_mission_control.go | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cmd/lncli/cmd_mc_cfg_get.go b/cmd/lncli/cmd_mc_cfg_get.go index 2062dcd923..d9b04dabd9 100644 --- a/cmd/lncli/cmd_mc_cfg_get.go +++ b/cmd/lncli/cmd_mc_cfg_get.go @@ -6,8 +6,9 @@ import ( ) var getCfgCommand = cli.Command{ - Name: "getmccfg", - Usage: "Display mission control's config.", + Name: "getmccfg", + Category: "Mission Control", + Usage: "Display mission control's config.", Description: ` Returns the config currently being used by mission control. `, diff --git a/cmd/lncli/cmd_mc_cfg_set.go b/cmd/lncli/cmd_mc_cfg_set.go index 7e6fcc9368..2d1d8cc90a 100644 --- a/cmd/lncli/cmd_mc_cfg_set.go +++ b/cmd/lncli/cmd_mc_cfg_set.go @@ -6,8 +6,9 @@ import ( ) var setCfgCommand = cli.Command{ - Name: "setmccfg", - Usage: "Set mission control's config.", + Name: "setmccfg", + Category: "Mission Control", + Usage: "Set mission control's config.", Description: ` Update the config values being used by mission control to calculate the probability that payment routes will succeed. diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go index 68007205c0..d66ab7a39a 100644 --- a/cmd/lncli/cmd_query_mission_control.go +++ b/cmd/lncli/cmd_query_mission_control.go @@ -8,7 +8,7 @@ import ( var queryMissionControlCommand = cli.Command{ Name: "querymc", - Category: "Payments", + Category: "Mission Control", Usage: "Query the internal mission control state.", Action: actionDecorator(queryMissionControl), } diff --git a/cmd/lncli/cmd_query_probability.go b/cmd/lncli/cmd_query_probability.go index c941d0890e..cdf4caab03 100644 --- a/cmd/lncli/cmd_query_probability.go +++ b/cmd/lncli/cmd_query_probability.go @@ -13,7 +13,7 @@ import ( var queryProbCommand = cli.Command{ Name: "queryprob", - Category: "Payments", + Category: "Mission Control", Usage: "Estimate a success probability.", ArgsUsage: "from-node to-node amt", Action: actionDecorator(queryProb), diff --git a/cmd/lncli/cmd_reset_mission_control.go b/cmd/lncli/cmd_reset_mission_control.go index f0fe9aa940..272a76042f 100644 --- a/cmd/lncli/cmd_reset_mission_control.go +++ b/cmd/lncli/cmd_reset_mission_control.go @@ -8,7 +8,7 @@ import ( var resetMissionControlCommand = cli.Command{ Name: "resetmc", - Category: "Payments", + Category: "Mission Control", Usage: "Reset internal mission control state.", Action: actionDecorator(resetMissionControl), } From 1d103f1f55bd6c855ae8fccc30eacfc09c97ceca Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 7 Sep 2021 13:14:57 +0200 Subject: [PATCH 3/5] cmd/lncli: group all Mission Control cmds into own file Having one file per sub command seems a bit too excessive and isn't really implemented for any of the other commands. So we just group the Mission Control commands into their own file for now. --- cmd/lncli/cmd_mc_cfg_get.go | 35 ---- cmd/lncli/cmd_mc_cfg_set.go | 101 ----------- cmd/lncli/cmd_mission_control.go | 236 +++++++++++++++++++++++++ cmd/lncli/cmd_query_mission_control.go | 32 ---- cmd/lncli/cmd_query_probability.go | 68 ------- cmd/lncli/cmd_reset_mission_control.go | 26 --- 6 files changed, 236 insertions(+), 262 deletions(-) delete mode 100644 cmd/lncli/cmd_mc_cfg_get.go delete mode 100644 cmd/lncli/cmd_mc_cfg_set.go create mode 100644 cmd/lncli/cmd_mission_control.go delete mode 100644 cmd/lncli/cmd_query_mission_control.go delete mode 100644 cmd/lncli/cmd_query_probability.go delete mode 100644 cmd/lncli/cmd_reset_mission_control.go diff --git a/cmd/lncli/cmd_mc_cfg_get.go b/cmd/lncli/cmd_mc_cfg_get.go deleted file mode 100644 index d9b04dabd9..0000000000 --- a/cmd/lncli/cmd_mc_cfg_get.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/urfave/cli" -) - -var getCfgCommand = cli.Command{ - Name: "getmccfg", - Category: "Mission Control", - Usage: "Display mission control's config.", - Description: ` - Returns the config currently being used by mission control. - `, - Action: actionDecorator(getCfg), -} - -func getCfg(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - resp, err := client.GetMissionControlConfig( - ctxc, &routerrpc.GetMissionControlConfigRequest{}, - ) - if err != nil { - return err - } - - printRespJSON(resp) - - return nil -} diff --git a/cmd/lncli/cmd_mc_cfg_set.go b/cmd/lncli/cmd_mc_cfg_set.go deleted file mode 100644 index 2d1d8cc90a..0000000000 --- a/cmd/lncli/cmd_mc_cfg_set.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/urfave/cli" -) - -var setCfgCommand = cli.Command{ - Name: "setmccfg", - Category: "Mission Control", - Usage: "Set mission control's config.", - Description: ` - Update the config values being used by mission control to calculate - the probability that payment routes will succeed. - `, - Flags: []cli.Flag{ - cli.DurationFlag{ - Name: "halflife", - Usage: "the amount of time taken to restore a node " + - "or channel to 50% probability of success.", - }, - cli.Float64Flag{ - Name: "hopprob", - Usage: "the probability of success assigned " + - "to hops that we have no information about", - }, - cli.Float64Flag{ - Name: "weight", - Usage: "the degree to which mission control should " + - "rely on historical results, expressed as " + - "value in [0;1]", - }, cli.UintFlag{ - Name: "pmtnr", - Usage: "the number of payments mission control " + - "should store", - }, - cli.DurationFlag{ - Name: "failrelax", - Usage: "the amount of time to wait after a failure " + - "before raising failure amount", - }, - }, - Action: actionDecorator(setCfg), -} - -func setCfg(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - resp, err := client.GetMissionControlConfig( - ctxc, &routerrpc.GetMissionControlConfigRequest{}, - ) - if err != nil { - return err - } - - var haveValue bool - - if ctx.IsSet("halflife") { - haveValue = true - resp.Config.HalfLifeSeconds = uint64(ctx.Duration( - "halflife", - ).Seconds()) - } - - if ctx.IsSet("hopprob") { - haveValue = true - resp.Config.HopProbability = float32(ctx.Float64("hopprob")) - } - - if ctx.IsSet("weight") { - haveValue = true - resp.Config.Weight = float32(ctx.Float64("weight")) - } - - if ctx.IsSet("pmtnr") { - haveValue = true - resp.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr")) - } - - if ctx.IsSet("failrelax") { - haveValue = true - resp.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration( - "failrelax", - ).Seconds()) - } - - if !haveValue { - return cli.ShowCommandHelp(ctx, "setmccfg") - } - - _, err = client.SetMissionControlConfig( - ctxc, &routerrpc.SetMissionControlConfigRequest{ - Config: resp.Config, - }, - ) - return err -} diff --git a/cmd/lncli/cmd_mission_control.go b/cmd/lncli/cmd_mission_control.go new file mode 100644 index 0000000000..92ce1269f4 --- /dev/null +++ b/cmd/lncli/cmd_mission_control.go @@ -0,0 +1,236 @@ +package main + +import ( + "fmt" + "strconv" + + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/urfave/cli" +) + +var getCfgCommand = cli.Command{ + Name: "getmccfg", + Category: "Mission Control", + Usage: "Display mission control's config.", + Description: ` + Returns the config currently being used by mission control. + `, + Action: actionDecorator(getCfg), +} + +func getCfg(ctx *cli.Context) error { + ctxc := getContext() + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + resp, err := client.GetMissionControlConfig( + ctxc, &routerrpc.GetMissionControlConfigRequest{}, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var setCfgCommand = cli.Command{ + Name: "setmccfg", + Category: "Mission Control", + Usage: "Set mission control's config.", + Description: ` + Update the config values being used by mission control to calculate + the probability that payment routes will succeed. + `, + Flags: []cli.Flag{ + cli.DurationFlag{ + Name: "halflife", + Usage: "the amount of time taken to restore a node " + + "or channel to 50% probability of success.", + }, + cli.Float64Flag{ + Name: "hopprob", + Usage: "the probability of success assigned " + + "to hops that we have no information about", + }, + cli.Float64Flag{ + Name: "weight", + Usage: "the degree to which mission control should " + + "rely on historical results, expressed as " + + "value in [0;1]", + }, cli.UintFlag{ + Name: "pmtnr", + Usage: "the number of payments mission control " + + "should store", + }, + cli.DurationFlag{ + Name: "failrelax", + Usage: "the amount of time to wait after a failure " + + "before raising failure amount", + }, + }, + Action: actionDecorator(setCfg), +} + +func setCfg(ctx *cli.Context) error { + ctxc := getContext() + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + resp, err := client.GetMissionControlConfig( + ctxc, &routerrpc.GetMissionControlConfigRequest{}, + ) + if err != nil { + return err + } + + var haveValue bool + + if ctx.IsSet("halflife") { + haveValue = true + resp.Config.HalfLifeSeconds = uint64(ctx.Duration( + "halflife", + ).Seconds()) + } + + if ctx.IsSet("hopprob") { + haveValue = true + resp.Config.HopProbability = float32(ctx.Float64("hopprob")) + } + + if ctx.IsSet("weight") { + haveValue = true + resp.Config.Weight = float32(ctx.Float64("weight")) + } + + if ctx.IsSet("pmtnr") { + haveValue = true + resp.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr")) + } + + if ctx.IsSet("failrelax") { + haveValue = true + resp.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration( + "failrelax", + ).Seconds()) + } + + if !haveValue { + return cli.ShowCommandHelp(ctx, "setmccfg") + } + + _, err = client.SetMissionControlConfig( + ctxc, &routerrpc.SetMissionControlConfigRequest{ + Config: resp.Config, + }, + ) + return err +} + +var queryMissionControlCommand = cli.Command{ + Name: "querymc", + Category: "Mission Control", + Usage: "Query the internal mission control state.", + Action: actionDecorator(queryMissionControl), +} + +func queryMissionControl(ctx *cli.Context) error { + ctxc := getContext() + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + req := &routerrpc.QueryMissionControlRequest{} + snapshot, err := client.QueryMissionControl(ctxc, req) + if err != nil { + return err + } + + printRespJSON(snapshot) + + return nil +} + +var queryProbCommand = cli.Command{ + Name: "queryprob", + Category: "Mission Control", + Usage: "Estimate a success probability.", + ArgsUsage: "from-node to-node amt", + Action: actionDecorator(queryProb), +} + +func queryProb(ctx *cli.Context) error { + ctxc := getContext() + args := ctx.Args() + + if len(args) != 3 { + return cli.ShowCommandHelp(ctx, "queryprob") + } + + fromNode, err := route.NewVertexFromStr(args.Get(0)) + if err != nil { + return fmt.Errorf("invalid from node key: %v", err) + } + + toNode, err := route.NewVertexFromStr(args.Get(1)) + if err != nil { + return fmt.Errorf("invalid to node key: %v", err) + } + + amtSat, err := strconv.ParseUint(args.Get(2), 10, 64) + if err != nil { + return fmt.Errorf("invalid amt: %v", err) + } + + amtMsat := lnwire.NewMSatFromSatoshis( + btcutil.Amount(amtSat), + ) + + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + req := &routerrpc.QueryProbabilityRequest{ + FromNode: fromNode[:], + ToNode: toNode[:], + AmtMsat: int64(amtMsat), + } + + response, err := client.QueryProbability(ctxc, req) + if err != nil { + return err + } + + printRespJSON(response) + + return nil +} + +var resetMissionControlCommand = cli.Command{ + Name: "resetmc", + Category: "Mission Control", + Usage: "Reset internal mission control state.", + Action: actionDecorator(resetMissionControl), +} + +func resetMissionControl(ctx *cli.Context) error { + ctxc := getContext() + conn := getClientConn(ctx, false) + defer conn.Close() + + client := routerrpc.NewRouterClient(conn) + + req := &routerrpc.ResetMissionControlRequest{} + _, err := client.ResetMissionControl(ctxc, req) + return err +} diff --git a/cmd/lncli/cmd_query_mission_control.go b/cmd/lncli/cmd_query_mission_control.go deleted file mode 100644 index d66ab7a39a..0000000000 --- a/cmd/lncli/cmd_query_mission_control.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - - "github.com/urfave/cli" -) - -var queryMissionControlCommand = cli.Command{ - Name: "querymc", - Category: "Mission Control", - Usage: "Query the internal mission control state.", - Action: actionDecorator(queryMissionControl), -} - -func queryMissionControl(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - req := &routerrpc.QueryMissionControlRequest{} - snapshot, err := client.QueryMissionControl(ctxc, req) - if err != nil { - return err - } - - printRespJSON(snapshot) - - return nil -} diff --git a/cmd/lncli/cmd_query_probability.go b/cmd/lncli/cmd_query_probability.go deleted file mode 100644 index cdf4caab03..0000000000 --- a/cmd/lncli/cmd_query_probability.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - - "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" -) - -var queryProbCommand = cli.Command{ - Name: "queryprob", - Category: "Mission Control", - Usage: "Estimate a success probability.", - ArgsUsage: "from-node to-node amt", - Action: actionDecorator(queryProb), -} - -func queryProb(ctx *cli.Context) error { - ctxc := getContext() - args := ctx.Args() - - if len(args) != 3 { - return cli.ShowCommandHelp(ctx, "queryprob") - } - - fromNode, err := route.NewVertexFromStr(args.Get(0)) - if err != nil { - return fmt.Errorf("invalid from node key: %v", err) - } - - toNode, err := route.NewVertexFromStr(args.Get(1)) - if err != nil { - return fmt.Errorf("invalid to node key: %v", err) - } - - amtSat, err := strconv.ParseUint(args.Get(2), 10, 64) - if err != nil { - return fmt.Errorf("invalid amt: %v", err) - } - - amtMsat := lnwire.NewMSatFromSatoshis( - btcutil.Amount(amtSat), - ) - - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - req := &routerrpc.QueryProbabilityRequest{ - FromNode: fromNode[:], - ToNode: toNode[:], - AmtMsat: int64(amtMsat), - } - - response, err := client.QueryProbability(ctxc, req) - if err != nil { - return err - } - - printRespJSON(response) - - return nil -} diff --git a/cmd/lncli/cmd_reset_mission_control.go b/cmd/lncli/cmd_reset_mission_control.go deleted file mode 100644 index 272a76042f..0000000000 --- a/cmd/lncli/cmd_reset_mission_control.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - - "github.com/urfave/cli" -) - -var resetMissionControlCommand = cli.Command{ - Name: "resetmc", - Category: "Mission Control", - Usage: "Reset internal mission control state.", - Action: actionDecorator(resetMissionControl), -} - -func resetMissionControl(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - req := &routerrpc.ResetMissionControlRequest{} - _, err := client.ResetMissionControl(ctxc, req) - return err -} From 348405994cb61e5fc072bfde00167c82495da071 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 7 Sep 2021 13:41:51 +0200 Subject: [PATCH 4/5] cmd/lncli: add deletepayments command To give the CLI user the option to delete a single or multiple payments in one command, we expose the DeletePayment and DeleteAllPayments RPCs to the command line as well. Due to the similarity of the two RPCs we combine them into a single command with multiple flags. To make the command a bit more safe to run without arguments, we consciously switch the logic of the RPC flag "failed_payments_only" which is false by default into a "--include_non_failed" in the CLI which is false by thefault. So a user running the command without knowing what they are doing are only deleting failed payments by default, not all of the payments. --- cmd/lncli/cmd_payments.go | 138 ++++++++++++++++++++++++++++++++++++++ cmd/lncli/main.go | 1 + 2 files changed, 139 insertions(+) diff --git a/cmd/lncli/cmd_payments.go b/cmd/lncli/cmd_payments.go index f6ee04b2bd..82808e3fc7 100644 --- a/cmd/lncli/cmd_payments.go +++ b/cmd/lncli/cmd_payments.go @@ -1394,6 +1394,144 @@ func buildRoute(ctx *cli.Context) error { return nil } +var deletePaymentsCommand = cli.Command{ + Name: "deletepayments", + Category: "Payments", + Usage: "Delete a single or multiple payments from the database.", + ArgsUsage: "--all [--failed_htlcs_only --include_non_failed] | " + + "--payment_hash hash [--failed_htlcs_only]", + Description: ` + This command either deletes all failed payments or a single payment from + the database to reclaim disk space. + + If the --all flag is used, then all failed payments are removed. If so + desired, _ALL_ payments (even the successful ones) can be deleted + by additionally specifying --include_non_failed. + + If a --payment_hash is specified, that single payment is deleted, + independent of its state. + + If --failed_htlcs_only is specified then the payments themselves (or the + single payment itself if used with --payment_hash) is not deleted, only + the information about any failed HTLC attempts during the payment. + + NOTE: Removing payments from the database does free up disk space within + the internal bbolt database. But that disk space is only reclaimed after + compacting the database. Users might want to turn on auto compaction + (db.bolt.auto-compact=true in the config file or --db.bolt.auto-compact + as a command line flag) and restart lnd after deleting a large number of + payments to see a reduction in the file size of the channel.db file. + `, + Action: actionDecorator(deletePayments), + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "all", + Usage: "delete all failed payments", + }, + cli.StringFlag{ + Name: "payment_hash", + Usage: "delete a specific payment identified by its " + + "payment hash", + }, + cli.BoolFlag{ + Name: "failed_htlcs_only", + Usage: "only delete failed HTLCs from payments, not " + + "the payment itself", + }, + cli.BoolFlag{ + Name: "include_non_failed", + Usage: "delete ALL payments, not just the failed ones", + }, + }, +} + +func deletePayments(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getClient(ctx) + defer cleanUp() + + // Show command help if arguments or no flags are provided. + if ctx.NArg() > 0 || ctx.NumFlags() == 0 { + _ = cli.ShowCommandHelp(ctx, "deletepayments") + return nil + } + + var ( + paymentHash []byte + all = ctx.Bool("all") + singlePayment = ctx.IsSet("payment_hash") + failedHTLCsOnly = ctx.Bool("failed_htlcs_only") + includeNonFailed = ctx.Bool("include_non_failed") + err error + okMsg = struct { + OK bool `json:"ok"` + }{ + OK: true, + } + ) + + // We pack two RPCs into the same CLI so there are a few non-valid + // combinations of the flags we need to filter out. + switch { + case all && singlePayment: + return fmt.Errorf("cannot use --all and --payment_hash at " + + "the same time") + + case singlePayment && includeNonFailed: + return fmt.Errorf("cannot use --payment_hash and " + + "--include_non_failed at the same time, when using " + + "a payment hash the payment is deleted independent " + + "of its state") + } + + // Deleting a single payment is implemented in a different RPC than + // removing all/multiple payments. + switch { + case singlePayment: + paymentHash, err = hex.DecodeString(ctx.String("payment_hash")) + if err != nil { + return fmt.Errorf("error decoding payment_hash: %v", + err) + } + + _, err = client.DeletePayment(ctxc, &lnrpc.DeletePaymentRequest{ + PaymentHash: paymentHash, + FailedHtlcsOnly: failedHTLCsOnly, + }) + if err != nil { + return fmt.Errorf("error deleting single payment: %v", + err) + } + + case all: + what := "failed" + if includeNonFailed { + what = "all" + } + if failedHTLCsOnly { + what = fmt.Sprintf("failed HTLCs from %s", what) + } + + fmt.Printf("Removing %s payments, this might take a while...\n", + what) + _, err = client.DeleteAllPayments( + ctxc, &lnrpc.DeleteAllPaymentsRequest{ + FailedPaymentsOnly: !includeNonFailed, + FailedHtlcsOnly: failedHTLCsOnly, + }, + ) + if err != nil { + return fmt.Errorf("error deleting payments: %v", err) + } + } + + // Users are confused by empty JSON outputs so let's return a simple OK + // instead of just printing the empty response RPC message. + printJSON(okMsg) + + return nil +} + // ESC is the ASCII code for escape character const ESC = 27 diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 83b9d1df5c..731b05686c 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -383,6 +383,7 @@ func main() { versionCommand, profileSubCommand, getStateCommand, + deletePaymentsCommand, } // Add any extra commands determined by build flags. From 57d203c9f138b99a546cdb52109aff8a406c952d Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Sep 2021 12:33:58 +0200 Subject: [PATCH 5/5] docs: add release notes --- docs/release-notes/release-notes-0.14.0.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index c0717d5b4b..4d17017f4d 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -211,6 +211,11 @@ you. with the `addInvoice` rpc interface. However, now the function has been [exposed in the go package `invoicesrpc`](https://github.com/lightningnetwork/lnd/pull/5697). +* The `DeleteAllPayments` and `DeletePayment` RPC methods can now be called from + the command line with the [new + `lncli deletepayments`](https://github.com/lightningnetwork/lnd/pull/5699) + command. + ## Code Health ### Code cleanup, refactor, typo fixes