Merge pull request #5699 from guggero/lncli-deletepayment

lncli: add deletepayments command
This commit is contained in:
Oliver Gugger 2021-09-27 13:41:41 +02:00 committed by GitHub
commit f60a1ba0ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 787 additions and 679 deletions

View File

@ -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
}

View File

@ -1,34 +0,0 @@
package main
import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/urfave/cli"
)
var getCfgCommand = cli.Command{
Name: "getmccfg",
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
}

View File

@ -1,100 +0,0 @@
package main
import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/urfave/cli"
)
var setCfgCommand = cli.Command{
Name: "setmccfg",
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
}

View File

@ -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
}

View File

@ -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,550 @@ 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
}
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

View File

@ -1,32 +0,0 @@
package main
import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/urfave/cli"
)
var queryMissionControlCommand = cli.Command{
Name: "querymc",
Category: "Payments",
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
}

View File

@ -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: "Payments",
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
}

View File

@ -1,26 +0,0 @@
package main
import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/urfave/cli"
)
var resetMissionControlCommand = cli.Command{
Name: "resetmc",
Category: "Payments",
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
}

View File

@ -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",

View File

@ -383,6 +383,7 @@ func main() {
versionCommand,
profileSubCommand,
getStateCommand,
deletePaymentsCommand,
}
// Add any extra commands determined by build flags.

View File

@ -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