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.
This commit is contained in:
Oliver Gugger 2021-09-07 13:08:47 +02:00
parent e686a70b96
commit da418ef46b
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
3 changed files with 407 additions and 419 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

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

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