cmd/lncli: add watchtower client commands
This commit is contained in:
4 files changed
+346
No files matched your search
@@ -306,6 +306,7 @@ func main() {
|
|||||||
app.Commands = append(app.Commands, routerCommands()...)
|
app.Commands = append(app.Commands, routerCommands()...)
|
||||||
app.Commands = append(app.Commands, walletCommands()...)
|
app.Commands = append(app.Commands, walletCommands()...)
|
||||||
app.Commands = append(app.Commands, watchtowerCommands()...)
|
app.Commands = append(app.Commands, watchtowerCommands()...)
|
||||||
|
app.Commands = append(app.Commands, wtclientCommands()...)
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
// +build wtclientrpc
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wtclientCommands will return nil for non-wtclientrpc builds.
|
||||||
|
func wtclientCommands() []cli.Command {
|
||||||
|
return []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "wtclient",
|
||||||
|
Usage: "Interact with the watchtower client.",
|
||||||
|
Category: "Watchtower",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
addTowerCommand,
|
||||||
|
removeTowerCommand,
|
||||||
|
listTowersCommand,
|
||||||
|
getTowerCommand,
|
||||||
|
statsCommand,
|
||||||
|
policyCommand,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWtclient initializes a connection to the watchtower client RPC in order to
|
||||||
|
// interact with it.
|
||||||
|
func getWtclient(ctx *cli.Context) (wtclientrpc.WatchtowerClientClient, func()) {
|
||||||
|
conn := getClientConn(ctx, false)
|
||||||
|
cleanUp := func() {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return wtclientrpc.NewWatchtowerClientClient(conn), cleanUp
|
||||||
|
}
|
||||||
|
|
||||||
|
var addTowerCommand = cli.Command{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "Register a watchtower to use for future sessions/backups.",
|
||||||
|
Description: "If the watchtower has already been registered, then " +
|
||||||
|
"this command serves as a way of updating the watchtower " +
|
||||||
|
"with new addresses it is reachable over.",
|
||||||
|
ArgsUsage: "pubkey@address",
|
||||||
|
Action: actionDecorator(addTower),
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTower(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "add")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(ctx.Args().First(), "@")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return errors.New("expected tower of format pubkey@address")
|
||||||
|
}
|
||||||
|
pubKey, err := hex.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid public key: %v", err)
|
||||||
|
}
|
||||||
|
address := parts[1]
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.AddTowerRequest{
|
||||||
|
Pubkey: pubKey,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
resp, err := client.AddTower(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRespJSON(resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeTowerCommand = cli.Command{
|
||||||
|
Name: "remove",
|
||||||
|
Usage: "Remove a watchtower to prevent its use for future " +
|
||||||
|
"sessions/backups.",
|
||||||
|
Description: "An optional address can be provided to remove, " +
|
||||||
|
"indicating that the watchtower is no longer reachable at " +
|
||||||
|
"this address. If an address isn't provided, then the " +
|
||||||
|
"watchtower will no longer be used for future sessions/backups.",
|
||||||
|
ArgsUsage: "pubkey | pubkey@address",
|
||||||
|
Action: actionDecorator(removeTower),
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTower(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The command can have only one argument, but it can be interpreted in
|
||||||
|
// either of the following formats:
|
||||||
|
//
|
||||||
|
// pubkey or pubkey@address
|
||||||
|
//
|
||||||
|
// The hex-encoded public key of the watchtower is always required,
|
||||||
|
// while the second is an optional address we'll remove from the
|
||||||
|
// watchtower's database record.
|
||||||
|
parts := strings.Split(ctx.Args().First(), "@")
|
||||||
|
if len(parts) > 2 {
|
||||||
|
return errors.New("expected tower of format pubkey@address")
|
||||||
|
}
|
||||||
|
pubKey, err := hex.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid public key: %v", err)
|
||||||
|
}
|
||||||
|
var address string
|
||||||
|
if len(parts) == 2 {
|
||||||
|
address = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.RemoveTowerRequest{
|
||||||
|
Pubkey: pubKey,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
resp, err := client.RemoveTower(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRespJSON(resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var listTowersCommand = cli.Command{
|
||||||
|
Name: "towers",
|
||||||
|
Usage: "Display information about all registered watchtowers.",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "include_sessions",
|
||||||
|
Usage: "include sessions with the watchtower in the " +
|
||||||
|
"response",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(listTowers),
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTowers(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() > 0 || ctx.NumFlags() > 1 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "towers")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.ListTowersRequest{
|
||||||
|
IncludeSessions: ctx.Bool("include_sessions"),
|
||||||
|
}
|
||||||
|
resp, err := client.ListTowers(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var listTowersResp = struct {
|
||||||
|
Towers []*Tower `json:"towers"`
|
||||||
|
}{
|
||||||
|
Towers: make([]*Tower, len(resp.Towers)),
|
||||||
|
}
|
||||||
|
for i, tower := range resp.Towers {
|
||||||
|
listTowersResp.Towers[i] = NewTowerFromProto(tower)
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(listTowersResp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var getTowerCommand = cli.Command{
|
||||||
|
Name: "tower",
|
||||||
|
Usage: "Display information about a specific registered watchtower.",
|
||||||
|
ArgsUsage: "pubkey",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "include_sessions",
|
||||||
|
Usage: "include sessions with the watchtower in the " +
|
||||||
|
"response",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(getTower),
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTower(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() != 1 || ctx.NumFlags() > 1 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "tower")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The command only has one argument, which we expect to be the
|
||||||
|
// hex-encoded public key of the watchtower we'll display information
|
||||||
|
// about.
|
||||||
|
pubKey, err := hex.DecodeString(ctx.Args().Get(0))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.GetTowerInfoRequest{
|
||||||
|
Pubkey: pubKey,
|
||||||
|
IncludeSessions: ctx.Bool("include_sessions"),
|
||||||
|
}
|
||||||
|
resp, err := client.GetTowerInfo(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(NewTowerFromProto(resp))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var statsCommand = cli.Command{
|
||||||
|
Name: "stats",
|
||||||
|
Usage: "Display the session stats of the watchtower client.",
|
||||||
|
Action: actionDecorator(stats),
|
||||||
|
}
|
||||||
|
|
||||||
|
func stats(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() > 0 || ctx.NumFlags() > 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.StatsRequest{}
|
||||||
|
resp, err := client.Stats(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRespJSON(resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var policyCommand = cli.Command{
|
||||||
|
Name: "policy",
|
||||||
|
Usage: "Display the active watchtower client policy configuration.",
|
||||||
|
Action: actionDecorator(policy),
|
||||||
|
}
|
||||||
|
|
||||||
|
func policy(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if the number of arguments/flags
|
||||||
|
// is not what we expect.
|
||||||
|
if ctx.NArg() > 0 || ctx.NumFlags() > 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanUp := getWtclient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
req := &wtclientrpc.PolicyRequest{}
|
||||||
|
resp, err := client.Policy(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRespJSON(resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// +build !wtclientrpc
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
|
// wtclientCommands will return nil for non-wtclientrpc builds.
|
||||||
|
func wtclientCommands() []cli.Command {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// +build wtclientrpc
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TowerSession encompasses information about a tower session.
|
||||||
|
type TowerSession struct {
|
||||||
|
NumBackups uint32 `json:"num_backups"`
|
||||||
|
NumPendingBackups uint32 `json:"num_pending_backups"`
|
||||||
|
MaxBackups uint32 `json:"max_backups"`
|
||||||
|
SweepSatPerByte uint32 `json:"sweep_sat_per_byte"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTowerSessionsFromProto converts a set of tower sessions from their RPC
|
||||||
|
// type to a CLI-friendly type.
|
||||||
|
func NewTowerSessionsFromProto(sessions []*wtclientrpc.TowerSession) []*TowerSession {
|
||||||
|
towerSessions := make([]*TowerSession, 0, len(sessions))
|
||||||
|
for _, session := range sessions {
|
||||||
|
towerSessions = append(towerSessions, &TowerSession{
|
||||||
|
NumBackups: session.NumBackups,
|
||||||
|
NumPendingBackups: session.NumPendingBackups,
|
||||||
|
MaxBackups: session.MaxBackups,
|
||||||
|
SweepSatPerByte: session.SweepSatPerByte,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return towerSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tower encompasses information about a registered watchtower.
|
||||||
|
type Tower struct {
|
||||||
|
PubKey string `json:"pubkey"`
|
||||||
|
Addresses []string `json:"addresses"`
|
||||||
|
ActiveSessionCandidate bool `json:"active_session_candidate"`
|
||||||
|
NumSessions uint32 `json:"num_sessions"`
|
||||||
|
Sessions []*TowerSession `json:"sessions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTowerFromProto converts a tower from its RPC type to a CLI-friendly type.
|
||||||
|
func NewTowerFromProto(tower *wtclientrpc.Tower) *Tower {
|
||||||
|
return &Tower{
|
||||||
|
PubKey: hex.EncodeToString(tower.Pubkey),
|
||||||
|
Addresses: tower.Addresses,
|
||||||
|
ActiveSessionCandidate: tower.ActiveSessionCandidate,
|
||||||
|
NumSessions: tower.NumSessions,
|
||||||
|
Sessions: NewTowerSessionsFromProto(tower.Sessions),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user