mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-26 01:33:02 +01:00
cmd/lncli: move commands and export
This commit is contained in:
parent
1858e1966f
commit
b4731b3d2d
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"regexp"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,7 +1,7 @@
|
||||
//go:build autopilotrpc
|
||||
// +build autopilotrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
|
@ -1,7 +1,7 @@
|
||||
//go:build !autopilotrpc
|
||||
// +build !autopilotrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,7 +1,7 @@
|
||||
//go:build chainrpc
|
||||
// +build chainrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,7 +1,7 @@
|
||||
//go:build !chainrpc
|
||||
// +build !chainrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -65,7 +65,8 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to file): `
|
||||
)
|
||||
|
||||
// TODO(roasbeef): change default number of confirmations.
|
||||
var openChannelCommand = cli.Command{
|
||||
|
||||
var OpenChannelCommand = cli.Command{
|
||||
Name: "openchannel",
|
||||
Category: "Channels",
|
||||
Usage: "Open a channel to a node or an existing peer.",
|
||||
@ -284,10 +285,14 @@ var openChannelCommand = cli.Command{
|
||||
allowed length is 500 characters`,
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(openChannel),
|
||||
Action: actionDecorator(func(c *cli.Context) error {
|
||||
return OpenChannel(c, nil)
|
||||
}),
|
||||
}
|
||||
|
||||
func openChannel(ctx *cli.Context) error {
|
||||
func OpenChannel(ctx *cli.Context,
|
||||
reqDecorator RequestDecorator[*lnrpc.OpenChannelRequest]) error {
|
||||
|
||||
// TODO(roasbeef): add deadline to context
|
||||
ctxc := getContext()
|
||||
client, cleanUp := getClient(ctx)
|
||||
@ -451,6 +456,13 @@ func openChannel(ctx *cli.Context) error {
|
||||
return fmt.Errorf("unsupported channel type %v", channelType)
|
||||
}
|
||||
|
||||
if reqDecorator != nil {
|
||||
err = reqDecorator(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PSBT funding is a more involved, interactive process that is too
|
||||
// large to also fit into this already long function.
|
||||
if ctx.Bool("psbt") {
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -42,8 +43,44 @@ const (
|
||||
defaultUtxoMinConf = 1
|
||||
)
|
||||
|
||||
var errBadChanPoint = errors.New("expecting chan_point to be in format of: " +
|
||||
"txid:index")
|
||||
var (
|
||||
errBadChanPoint = errors.New(
|
||||
"expecting chan_point to be in format of: txid:index",
|
||||
)
|
||||
|
||||
customDataPattern = regexp.MustCompile(
|
||||
`"custom_channel_data":\s*"([0-9a-z]+)"`,
|
||||
)
|
||||
)
|
||||
|
||||
func replaceCustomData(jsonBytes []byte) ([]byte, error) {
|
||||
if customDataPattern.Match(jsonBytes) {
|
||||
jsonBytes = customDataPattern.ReplaceAllFunc(
|
||||
jsonBytes, func(match []byte) []byte {
|
||||
encoded := customDataPattern.FindStringSubmatch(
|
||||
string(match),
|
||||
)[1]
|
||||
decoded, err := hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return match
|
||||
}
|
||||
|
||||
return []byte("\"custom_channel_data\":" +
|
||||
string(decoded))
|
||||
},
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := json.Indent(&buf, jsonBytes, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsonBytes = buf.Bytes()
|
||||
}
|
||||
|
||||
return jsonBytes, nil
|
||||
}
|
||||
|
||||
func getContext() context.Context {
|
||||
shutdownInterceptor, err := signal.Intercept()
|
||||
@ -67,7 +104,7 @@ func printJSON(resp interface{}) {
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
json.Indent(&out, b, "", "\t")
|
||||
json.Indent(&out, b, "", " ")
|
||||
out.WriteString("\n")
|
||||
out.WriteTo(os.Stdout)
|
||||
}
|
||||
@ -79,7 +116,13 @@ func printRespJSON(resp proto.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", jsonBytes)
|
||||
jsonBytesReplaced, err := replaceCustomData(jsonBytes)
|
||||
if err != nil {
|
||||
fmt.Println("unable to replace custom data: ", err)
|
||||
jsonBytesReplaced = jsonBytes
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", jsonBytesReplaced)
|
||||
}
|
||||
|
||||
// actionDecorator is used to add additional information and error handling
|
||||
@ -1553,7 +1596,11 @@ func pendingChannels(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var listChannelsCommand = cli.Command{
|
||||
type RequestDecorator[T proto.Message] func(*cli.Context, T) error
|
||||
|
||||
type ResponseDecorator[T proto.Message] func(*cli.Context, T) error
|
||||
|
||||
var ListChannelsCommand = cli.Command{
|
||||
Name: "listchannels",
|
||||
Category: "Channels",
|
||||
Usage: "List all open channels.",
|
||||
@ -1586,35 +1633,14 @@ var listChannelsCommand = cli.Command{
|
||||
"order to improve performance",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(listChannels),
|
||||
Action: actionDecorator(func(c *cli.Context) error {
|
||||
return ListChannels(c, nil)
|
||||
}),
|
||||
}
|
||||
|
||||
var listAliasesCommand = cli.Command{
|
||||
Name: "listaliases",
|
||||
Category: "Channels",
|
||||
Usage: "List all aliases.",
|
||||
Flags: []cli.Flag{},
|
||||
Action: actionDecorator(listaliases),
|
||||
}
|
||||
func ListChannels(ctx *cli.Context,
|
||||
respDecorator ResponseDecorator[*lnrpc.ListChannelsResponse]) error {
|
||||
|
||||
func listaliases(ctx *cli.Context) error {
|
||||
ctxc := getContext()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
req := &lnrpc.ListAliasesRequest{}
|
||||
|
||||
resp, err := client.ListAliases(ctxc, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listChannels(ctx *cli.Context) error {
|
||||
ctxc := getContext()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
@ -1651,6 +1677,38 @@ func listChannels(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if respDecorator != nil {
|
||||
err = respDecorator(ctx, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var listAliasesCommand = cli.Command{
|
||||
Name: "listaliases",
|
||||
Category: "Channels",
|
||||
Usage: "List all aliases.",
|
||||
Flags: []cli.Flag{},
|
||||
Action: actionDecorator(listAliases),
|
||||
}
|
||||
|
||||
func listAliases(ctx *cli.Context) error {
|
||||
ctxc := getContext()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
req := &lnrpc.ListAliasesRequest{}
|
||||
|
||||
resp, err := client.ListAliases(ctxc, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
@ -120,3 +120,55 @@ func TestParseTimeLockDelta(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceCustomData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data string
|
||||
replaceData string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no replacement necessary",
|
||||
data: "foo",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "valid json with replacement",
|
||||
data: "{\"foo\":\"bar\",\"custom_channel_data\":\"" +
|
||||
hex.EncodeToString([]byte(
|
||||
"{\"bar\":\"baz\"}",
|
||||
)) + "\"}",
|
||||
expected: `{
|
||||
"foo": "bar",
|
||||
"custom_channel_data": {
|
||||
"bar": "baz"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "valid json with replacement and space",
|
||||
data: "{\"foo\":\"bar\",\"custom_channel_data\": \"" +
|
||||
hex.EncodeToString([]byte(
|
||||
"{\"bar\":\"baz\"}",
|
||||
)) + "\"}",
|
||||
expected: `{
|
||||
"foo": "bar",
|
||||
"custom_channel_data": {
|
||||
"bar": "baz"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := replaceCustomData([]byte(tc.data))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expected, string(result))
|
||||
})
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,7 +1,7 @@
|
||||
//go:build !dev
|
||||
// +build !dev
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,7 +1,7 @@
|
||||
//go:build invoicesrpc
|
||||
// +build invoicesrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -1,7 +1,7 @@
|
||||
//go:build !invoicesrpc
|
||||
// +build !invoicesrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
594
cmd/commands/main.go
Normal file
594
cmd/commands/main.go
Normal file
@ -0,0 +1,594 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The Decred developers
|
||||
// Copyright (C) 2015-2022 The Lightning Network Developers
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightningnetwork/lnd"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDataDir = "data"
|
||||
defaultChainSubDir = "chain"
|
||||
defaultTLSCertFilename = "tls.cert"
|
||||
defaultMacaroonFilename = "admin.macaroon"
|
||||
defaultRPCPort = "10009"
|
||||
defaultRPCHostPort = "localhost:" + defaultRPCPort
|
||||
|
||||
envVarRPCServer = "LNCLI_RPCSERVER"
|
||||
envVarLNDDir = "LNCLI_LNDDIR"
|
||||
envVarSOCKSProxy = "LNCLI_SOCKSPROXY"
|
||||
envVarTLSCertPath = "LNCLI_TLSCERTPATH"
|
||||
envVarChain = "LNCLI_CHAIN"
|
||||
envVarNetwork = "LNCLI_NETWORK"
|
||||
envVarMacaroonPath = "LNCLI_MACAROONPATH"
|
||||
envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT"
|
||||
envVarMacaroonIP = "LNCLI_MACAROONIP"
|
||||
envVarProfile = "LNCLI_PROFILE"
|
||||
envVarMacFromJar = "LNCLI_MACFROMJAR"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultLndDir = btcutil.AppDataDir("lnd", false)
|
||||
defaultTLSCertPath = filepath.Join(DefaultLndDir, defaultTLSCertFilename)
|
||||
|
||||
// maxMsgRecvSize is the largest message our client will receive. We
|
||||
// set this to 200MiB atm.
|
||||
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
|
||||
)
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient, func()) {
|
||||
conn := getClientConn(ctx, true)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewWalletUnlockerClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) {
|
||||
conn := getClientConn(ctx, true)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewStateClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
|
||||
conn := getClientConn(ctx, false)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewLightningClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
// First, we'll get the selected stored profile or an ephemeral one
|
||||
// created from the global options in the CLI context.
|
||||
profile, err := getGlobalOptions(ctx, skipMacaroons)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not load global options: %w", err))
|
||||
}
|
||||
|
||||
// Create a dial options array.
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(
|
||||
addMetadataUnaryInterceptor(profile.Metadata),
|
||||
),
|
||||
grpc.WithStreamInterceptor(
|
||||
addMetaDataStreamInterceptor(profile.Metadata),
|
||||
),
|
||||
}
|
||||
|
||||
if profile.Insecure {
|
||||
opts = append(opts, grpc.WithInsecure())
|
||||
} else {
|
||||
// Load the specified TLS certificate.
|
||||
certPool, err := profile.cert()
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not create cert pool: %w", err))
|
||||
}
|
||||
|
||||
// Build transport credentials from the certificate pool. If
|
||||
// there is no certificate pool, we expect the server to use a
|
||||
// non-self-signed certificate such as a certificate obtained
|
||||
// from Let's Encrypt.
|
||||
var creds credentials.TransportCredentials
|
||||
if certPool != nil {
|
||||
creds = credentials.NewClientTLSFromCert(certPool, "")
|
||||
} else {
|
||||
// Fallback to the system pool. Using an empty tls
|
||||
// config is an alternative to x509.SystemCertPool().
|
||||
// That call is not supported on Windows.
|
||||
creds = credentials.NewTLS(&tls.Config{})
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
|
||||
// Only process macaroon credentials if --no-macaroons isn't set and
|
||||
// if we're not skipping macaroon processing.
|
||||
if !profile.NoMacaroons && !skipMacaroons {
|
||||
// Find out which macaroon to load.
|
||||
macName := profile.Macaroons.Default
|
||||
if ctx.GlobalIsSet("macfromjar") {
|
||||
macName = ctx.GlobalString("macfromjar")
|
||||
}
|
||||
var macEntry *macaroonEntry
|
||||
for _, entry := range profile.Macaroons.Jar {
|
||||
if entry.Name == macName {
|
||||
macEntry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
if macEntry == nil {
|
||||
fatal(fmt.Errorf("macaroon with name '%s' not found "+
|
||||
"in profile", macName))
|
||||
}
|
||||
|
||||
// Get and possibly decrypt the specified macaroon.
|
||||
//
|
||||
// TODO(guggero): Make it possible to cache the password so we
|
||||
// don't need to ask for it every time.
|
||||
mac, err := macEntry.loadMacaroon(readPassword)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not load macaroon: %w", err))
|
||||
}
|
||||
|
||||
macConstraints := []macaroons.Constraint{
|
||||
// We add a time-based constraint to prevent replay of
|
||||
// the macaroon. It's good for 60 seconds by default to
|
||||
// make up for any discrepancy between client and server
|
||||
// clocks, but leaking the macaroon before it becomes
|
||||
// invalid makes it possible for an attacker to reuse
|
||||
// the macaroon. In addition, the validity time of the
|
||||
// macaroon is extended by the time the server clock is
|
||||
// behind the client clock, or shortened by the time the
|
||||
// server clock is ahead of the client clock (or invalid
|
||||
// altogether if, in the latter case, this time is more
|
||||
// than 60 seconds).
|
||||
// TODO(aakselrod): add better anti-replay protection.
|
||||
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
|
||||
|
||||
// Lock macaroon down to a specific IP address.
|
||||
macaroons.IPLockConstraint(profile.Macaroons.IP),
|
||||
|
||||
// ... Add more constraints if needed.
|
||||
}
|
||||
|
||||
// Apply constraints to the macaroon.
|
||||
constrainedMac, err := macaroons.AddConstraints(
|
||||
mac, macConstraints...,
|
||||
)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Now we append the macaroon credentials to the dial options.
|
||||
cred, err := macaroons.NewMacaroonCredential(constrainedMac)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("error cloning mac: %w", err))
|
||||
}
|
||||
opts = append(opts, grpc.WithPerRPCCredentials(cred))
|
||||
}
|
||||
|
||||
// If a socksproxy server is specified we use a tor dialer
|
||||
// to connect to the grpc server.
|
||||
if ctx.GlobalIsSet("socksproxy") {
|
||||
socksProxy := ctx.GlobalString("socksproxy")
|
||||
torDialer := func(_ context.Context, addr string) (net.Conn,
|
||||
error) {
|
||||
|
||||
return tor.Dial(
|
||||
addr, socksProxy, false, false,
|
||||
tor.DefaultConnTimeout,
|
||||
)
|
||||
}
|
||||
opts = append(opts, grpc.WithContextDialer(torDialer))
|
||||
} else {
|
||||
// We need to use a custom dialer so we can also connect to
|
||||
// unix sockets and not just TCP addresses.
|
||||
genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
|
||||
opts = append(opts, grpc.WithContextDialer(genericDialer))
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
|
||||
|
||||
conn, err := grpc.Dial(profile.RPCServer, opts...)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("unable to connect to RPC server: %w", err))
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// unary call.
|
||||
func addMetadataUnaryInterceptor(
|
||||
md map[string]string) grpc.UnaryClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, method string, req, reply interface{},
|
||||
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
|
||||
opts ...grpc.CallOption) error {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return invoker(outCtx, method, req, reply, cc, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// stream call.
|
||||
func addMetaDataStreamInterceptor(
|
||||
md map[string]string) grpc.StreamClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return streamer(outCtx, desc, cc, method, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// contextWithMetaData appends the given metadata key-value pairs to the given
|
||||
// context.
|
||||
func contextWithMetadata(ctx context.Context,
|
||||
md map[string]string) context.Context {
|
||||
|
||||
kvPairs := make([]string, 0, 2*len(md))
|
||||
for k, v := range md {
|
||||
kvPairs = append(kvPairs, k, v)
|
||||
}
|
||||
|
||||
return metadata.AppendToOutgoingContext(ctx, kvPairs...)
|
||||
}
|
||||
|
||||
// extractPathArgs parses the TLS certificate and macaroon paths from the
|
||||
// command.
|
||||
func extractPathArgs(ctx *cli.Context) (string, string, error) {
|
||||
network := strings.ToLower(ctx.GlobalString("network"))
|
||||
switch network {
|
||||
case "mainnet", "testnet", "regtest", "simnet", "signet":
|
||||
default:
|
||||
return "", "", fmt.Errorf("unknown network: %v", network)
|
||||
}
|
||||
|
||||
// We'll now fetch the lnddir so we can make a decision on how to
|
||||
// properly read the macaroons (if needed) and also the cert. This will
|
||||
// either be the default, or will have been overwritten by the end
|
||||
// user.
|
||||
lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir"))
|
||||
|
||||
// If the macaroon path as been manually provided, then we'll only
|
||||
// target the specified file.
|
||||
var macPath string
|
||||
if ctx.GlobalString("macaroonpath") != "" {
|
||||
macPath = lncfg.CleanAndExpandPath(ctx.GlobalString("macaroonpath"))
|
||||
} else {
|
||||
// Otherwise, we'll go into the path:
|
||||
// lnddir/data/chain/<chain>/<network> in order to fetch the
|
||||
// macaroon that we need.
|
||||
macPath = filepath.Join(
|
||||
lndDir, defaultDataDir, defaultChainSubDir,
|
||||
lnd.BitcoinChainName, network, defaultMacaroonFilename,
|
||||
)
|
||||
}
|
||||
|
||||
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath"))
|
||||
|
||||
// If a custom lnd directory was set, we'll also check if custom paths
|
||||
// for the TLS cert and macaroon file were set as well. If not, we'll
|
||||
// override their paths so they can be found within the custom lnd
|
||||
// directory set. This allows us to set a custom lnd directory, along
|
||||
// with custom paths to the TLS cert and macaroon file.
|
||||
if lndDir != DefaultLndDir {
|
||||
tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
|
||||
}
|
||||
|
||||
return tlsCertPath, macPath, nil
|
||||
}
|
||||
|
||||
// checkNotBothSet accepts two flag names, a and b, and checks that only flag a
|
||||
// or flag b can be set, but not both. It returns the name of the flag or an
|
||||
// error.
|
||||
func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) {
|
||||
if ctx.IsSet(a) && ctx.IsSet(b) {
|
||||
return "", fmt.Errorf(
|
||||
"either %s or %s should be set, but not both", a, b,
|
||||
)
|
||||
}
|
||||
|
||||
if ctx.IsSet(a) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "lncli"
|
||||
app.Version = build.Version() + " commit=" + build.Commit
|
||||
app.Usage = "control plane for your Lightning Network Daemon (lnd)"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "rpcserver",
|
||||
Value: defaultRPCHostPort,
|
||||
Usage: "The host:port of LN daemon.",
|
||||
EnvVar: envVarRPCServer,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "lnddir",
|
||||
Value: DefaultLndDir,
|
||||
Usage: "The path to lnd's base directory.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarLNDDir,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "socksproxy",
|
||||
Usage: "The host:port of a SOCKS proxy through " +
|
||||
"which all connections to the LN " +
|
||||
"daemon will be established over.",
|
||||
EnvVar: envVarSOCKSProxy,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tlscertpath",
|
||||
Value: defaultTLSCertPath,
|
||||
Usage: "The path to lnd's TLS certificate.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarTLSCertPath,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "chain, c",
|
||||
Usage: "The chain lnd is running on, e.g. bitcoin.",
|
||||
Value: "bitcoin",
|
||||
EnvVar: envVarChain,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "network, n",
|
||||
Usage: "The network lnd is running on, e.g. mainnet, " +
|
||||
"testnet, etc.",
|
||||
Value: "mainnet",
|
||||
EnvVar: envVarNetwork,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-macaroons",
|
||||
Usage: "Disable macaroon authentication.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macaroonpath",
|
||||
Usage: "The path to macaroon file.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarMacaroonPath,
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "macaroontimeout",
|
||||
Value: 60,
|
||||
Usage: "Anti-replay macaroon validity time in " +
|
||||
"seconds.",
|
||||
EnvVar: envVarMacaroonTimeout,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macaroonip",
|
||||
Usage: "If set, lock macaroon to specific IP address.",
|
||||
EnvVar: envVarMacaroonIP,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "profile, p",
|
||||
Usage: "Instead of reading settings from command " +
|
||||
"line parameters or using the default " +
|
||||
"profile, use a specific profile. If " +
|
||||
"a default profile is set, this flag can be " +
|
||||
"set to an empty string to disable reading " +
|
||||
"values from the profiles file.",
|
||||
EnvVar: envVarProfile,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macfromjar",
|
||||
Usage: "Use this macaroon from the profile's " +
|
||||
"macaroon jar instead of the default one. " +
|
||||
"Can only be used if profiles are defined.",
|
||||
EnvVar: envVarMacFromJar,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "metadata",
|
||||
Usage: "This flag can be used to specify a key-value " +
|
||||
"pair that should be appended to the " +
|
||||
"outgoing context before the request is sent " +
|
||||
"to lnd. This flag may be specified multiple " +
|
||||
"times. The format is: \"key:value\".",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Usage: "Connect to the rpc server without TLS " +
|
||||
"authentication",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
createCommand,
|
||||
createWatchOnlyCommand,
|
||||
unlockCommand,
|
||||
changePasswordCommand,
|
||||
newAddressCommand,
|
||||
estimateFeeCommand,
|
||||
sendManyCommand,
|
||||
sendCoinsCommand,
|
||||
listUnspentCommand,
|
||||
connectCommand,
|
||||
disconnectCommand,
|
||||
OpenChannelCommand,
|
||||
batchOpenChannelCommand,
|
||||
closeChannelCommand,
|
||||
closeAllChannelsCommand,
|
||||
abandonChannelCommand,
|
||||
listPeersCommand,
|
||||
walletBalanceCommand,
|
||||
channelBalanceCommand,
|
||||
getInfoCommand,
|
||||
getDebugInfoCommand,
|
||||
encryptDebugPackageCommand,
|
||||
decryptDebugPackageCommand,
|
||||
getRecoveryInfoCommand,
|
||||
pendingChannelsCommand,
|
||||
sendPaymentCommand,
|
||||
payInvoiceCommand,
|
||||
sendToRouteCommand,
|
||||
addInvoiceCommand,
|
||||
lookupInvoiceCommand,
|
||||
listInvoicesCommand,
|
||||
ListChannelsCommand,
|
||||
closedChannelsCommand,
|
||||
listPaymentsCommand,
|
||||
describeGraphCommand,
|
||||
getNodeMetricsCommand,
|
||||
getChanInfoCommand,
|
||||
getNodeInfoCommand,
|
||||
queryRoutesCommand,
|
||||
getNetworkInfoCommand,
|
||||
debugLevelCommand,
|
||||
decodePayReqCommand,
|
||||
listChainTxnsCommand,
|
||||
stopCommand,
|
||||
signMessageCommand,
|
||||
verifyMessageCommand,
|
||||
feeReportCommand,
|
||||
updateChannelPolicyCommand,
|
||||
forwardingHistoryCommand,
|
||||
exportChanBackupCommand,
|
||||
verifyChanBackupCommand,
|
||||
restoreChanBackupCommand,
|
||||
bakeMacaroonCommand,
|
||||
listMacaroonIDsCommand,
|
||||
deleteMacaroonIDCommand,
|
||||
listPermissionsCommand,
|
||||
printMacaroonCommand,
|
||||
constrainMacaroonCommand,
|
||||
trackPaymentCommand,
|
||||
versionCommand,
|
||||
profileSubCommand,
|
||||
getStateCommand,
|
||||
deletePaymentsCommand,
|
||||
sendCustomCommand,
|
||||
subscribeCustomCommand,
|
||||
fishCompletionCommand,
|
||||
listAliasesCommand,
|
||||
estimateRouteFeeCommand,
|
||||
generateManPageCommand,
|
||||
}
|
||||
|
||||
// Add any extra commands determined by build flags.
|
||||
app.Commands = append(app.Commands, autopilotCommands()...)
|
||||
app.Commands = append(app.Commands, invoicesCommands()...)
|
||||
app.Commands = append(app.Commands, neutrinoCommands()...)
|
||||
app.Commands = append(app.Commands, routerCommands()...)
|
||||
app.Commands = append(app.Commands, walletCommands()...)
|
||||
app.Commands = append(app.Commands, watchtowerCommands()...)
|
||||
app.Commands = append(app.Commands, wtclientCommands()...)
|
||||
app.Commands = append(app.Commands, devCommands()...)
|
||||
app.Commands = append(app.Commands, peersCommands()...)
|
||||
app.Commands = append(app.Commands, chainCommands()...)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// readPassword reads a password from the terminal. This requires there to be an
|
||||
// actual TTY so passing in a password from stdin won't work.
|
||||
func readPassword(text string) ([]byte, error) {
|
||||
fmt.Print(text)
|
||||
|
||||
// The variable syscall.Stdin is of a different type in the Windows API
|
||||
// that's why we need the explicit cast. And of course the linter
|
||||
// doesn't like it either.
|
||||
pw, err := term.ReadPassword(int(syscall.Stdin)) // nolint:unconvert
|
||||
fmt.Println()
|
||||
return pw, err
|
||||
}
|
||||
|
||||
// networkParams parses the global network flag into a chaincfg.Params.
|
||||
func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
|
||||
network := strings.ToLower(ctx.GlobalString("network"))
|
||||
switch network {
|
||||
case "mainnet":
|
||||
return &chaincfg.MainNetParams, nil
|
||||
|
||||
case "testnet":
|
||||
return &chaincfg.TestNet3Params, nil
|
||||
|
||||
case "regtest":
|
||||
return &chaincfg.RegressionNetParams, nil
|
||||
|
||||
case "simnet":
|
||||
return &chaincfg.SimNetParams, nil
|
||||
|
||||
case "signet":
|
||||
return &chaincfg.SigNetParams, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown network: %v", network)
|
||||
}
|
||||
}
|
||||
|
||||
// parseCoinSelectionStrategy parses a coin selection strategy string
|
||||
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
|
||||
func parseCoinSelectionStrategy(ctx *cli.Context) (
|
||||
lnrpc.CoinSelectionStrategy, error) {
|
||||
|
||||
strategy := ctx.String(coinSelectionStrategyFlag.Name)
|
||||
if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
|
||||
nil
|
||||
}
|
||||
|
||||
switch strategy {
|
||||
case "global-config":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
|
||||
nil
|
||||
|
||||
case "largest":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
|
||||
|
||||
case "random":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown coin selection strategy "+
|
||||
"%v", strategy)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
//go:build neutrinorpc
|
||||
// +build neutrinorpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
|
@ -1,7 +1,7 @@
|
||||
//go:build !neutrinorpc
|
||||
// +build !neutrinorpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,7 +1,7 @@
|
||||
//go:build peersrpc
|
||||
// +build peersrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,7 +1,7 @@
|
||||
//go:build !peersrpc
|
||||
// +build !peersrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -1,7 +1,7 @@
|
||||
//go:build walletrpc
|
||||
// +build walletrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,7 +1,7 @@
|
||||
//go:build !walletrpc
|
||||
// +build !walletrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
|
@ -1,7 +1,7 @@
|
||||
//go:build watchtowerrpc
|
||||
// +build watchtowerrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
@ -1,7 +1,7 @@
|
||||
//go:build !watchtowerrpc
|
||||
// +build !watchtowerrpc
|
||||
|
||||
package main
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -4,591 +4,8 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightningnetwork/lnd"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDataDir = "data"
|
||||
defaultChainSubDir = "chain"
|
||||
defaultTLSCertFilename = "tls.cert"
|
||||
defaultMacaroonFilename = "admin.macaroon"
|
||||
defaultRPCPort = "10009"
|
||||
defaultRPCHostPort = "localhost:" + defaultRPCPort
|
||||
|
||||
envVarRPCServer = "LNCLI_RPCSERVER"
|
||||
envVarLNDDir = "LNCLI_LNDDIR"
|
||||
envVarSOCKSProxy = "LNCLI_SOCKSPROXY"
|
||||
envVarTLSCertPath = "LNCLI_TLSCERTPATH"
|
||||
envVarChain = "LNCLI_CHAIN"
|
||||
envVarNetwork = "LNCLI_NETWORK"
|
||||
envVarMacaroonPath = "LNCLI_MACAROONPATH"
|
||||
envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT"
|
||||
envVarMacaroonIP = "LNCLI_MACAROONIP"
|
||||
envVarProfile = "LNCLI_PROFILE"
|
||||
envVarMacFromJar = "LNCLI_MACFROMJAR"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLndDir = btcutil.AppDataDir("lnd", false)
|
||||
defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename)
|
||||
|
||||
// maxMsgRecvSize is the largest message our client will receive. We
|
||||
// set this to 200MiB atm.
|
||||
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
|
||||
)
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient, func()) {
|
||||
conn := getClientConn(ctx, true)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewWalletUnlockerClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) {
|
||||
conn := getClientConn(ctx, true)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewStateClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
|
||||
conn := getClientConn(ctx, false)
|
||||
|
||||
cleanUp := func() {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return lnrpc.NewLightningClient(conn), cleanUp
|
||||
}
|
||||
|
||||
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
||||
// First, we'll get the selected stored profile or an ephemeral one
|
||||
// created from the global options in the CLI context.
|
||||
profile, err := getGlobalOptions(ctx, skipMacaroons)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not load global options: %w", err))
|
||||
}
|
||||
|
||||
// Create a dial options array.
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(
|
||||
addMetadataUnaryInterceptor(profile.Metadata),
|
||||
),
|
||||
grpc.WithStreamInterceptor(
|
||||
addMetaDataStreamInterceptor(profile.Metadata),
|
||||
),
|
||||
}
|
||||
|
||||
if profile.Insecure {
|
||||
opts = append(opts, grpc.WithInsecure())
|
||||
} else {
|
||||
// Load the specified TLS certificate.
|
||||
certPool, err := profile.cert()
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not create cert pool: %w", err))
|
||||
}
|
||||
|
||||
// Build transport credentials from the certificate pool. If
|
||||
// there is no certificate pool, we expect the server to use a
|
||||
// non-self-signed certificate such as a certificate obtained
|
||||
// from Let's Encrypt.
|
||||
var creds credentials.TransportCredentials
|
||||
if certPool != nil {
|
||||
creds = credentials.NewClientTLSFromCert(certPool, "")
|
||||
} else {
|
||||
// Fallback to the system pool. Using an empty tls
|
||||
// config is an alternative to x509.SystemCertPool().
|
||||
// That call is not supported on Windows.
|
||||
creds = credentials.NewTLS(&tls.Config{})
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
|
||||
// Only process macaroon credentials if --no-macaroons isn't set and
|
||||
// if we're not skipping macaroon processing.
|
||||
if !profile.NoMacaroons && !skipMacaroons {
|
||||
// Find out which macaroon to load.
|
||||
macName := profile.Macaroons.Default
|
||||
if ctx.GlobalIsSet("macfromjar") {
|
||||
macName = ctx.GlobalString("macfromjar")
|
||||
}
|
||||
var macEntry *macaroonEntry
|
||||
for _, entry := range profile.Macaroons.Jar {
|
||||
if entry.Name == macName {
|
||||
macEntry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
if macEntry == nil {
|
||||
fatal(fmt.Errorf("macaroon with name '%s' not found "+
|
||||
"in profile", macName))
|
||||
}
|
||||
|
||||
// Get and possibly decrypt the specified macaroon.
|
||||
//
|
||||
// TODO(guggero): Make it possible to cache the password so we
|
||||
// don't need to ask for it every time.
|
||||
mac, err := macEntry.loadMacaroon(readPassword)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("could not load macaroon: %w", err))
|
||||
}
|
||||
|
||||
macConstraints := []macaroons.Constraint{
|
||||
// We add a time-based constraint to prevent replay of
|
||||
// the macaroon. It's good for 60 seconds by default to
|
||||
// make up for any discrepancy between client and server
|
||||
// clocks, but leaking the macaroon before it becomes
|
||||
// invalid makes it possible for an attacker to reuse
|
||||
// the macaroon. In addition, the validity time of the
|
||||
// macaroon is extended by the time the server clock is
|
||||
// behind the client clock, or shortened by the time the
|
||||
// server clock is ahead of the client clock (or invalid
|
||||
// altogether if, in the latter case, this time is more
|
||||
// than 60 seconds).
|
||||
// TODO(aakselrod): add better anti-replay protection.
|
||||
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
|
||||
|
||||
// Lock macaroon down to a specific IP address.
|
||||
macaroons.IPLockConstraint(profile.Macaroons.IP),
|
||||
|
||||
// ... Add more constraints if needed.
|
||||
}
|
||||
|
||||
// Apply constraints to the macaroon.
|
||||
constrainedMac, err := macaroons.AddConstraints(
|
||||
mac, macConstraints...,
|
||||
)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Now we append the macaroon credentials to the dial options.
|
||||
cred, err := macaroons.NewMacaroonCredential(constrainedMac)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("error cloning mac: %w", err))
|
||||
}
|
||||
opts = append(opts, grpc.WithPerRPCCredentials(cred))
|
||||
}
|
||||
|
||||
// If a socksproxy server is specified we use a tor dialer
|
||||
// to connect to the grpc server.
|
||||
if ctx.GlobalIsSet("socksproxy") {
|
||||
socksProxy := ctx.GlobalString("socksproxy")
|
||||
torDialer := func(_ context.Context, addr string) (net.Conn,
|
||||
error) {
|
||||
|
||||
return tor.Dial(
|
||||
addr, socksProxy, false, false,
|
||||
tor.DefaultConnTimeout,
|
||||
)
|
||||
}
|
||||
opts = append(opts, grpc.WithContextDialer(torDialer))
|
||||
} else {
|
||||
// We need to use a custom dialer so we can also connect to
|
||||
// unix sockets and not just TCP addresses.
|
||||
genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
|
||||
opts = append(opts, grpc.WithContextDialer(genericDialer))
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
|
||||
|
||||
conn, err := grpc.Dial(profile.RPCServer, opts...)
|
||||
if err != nil {
|
||||
fatal(fmt.Errorf("unable to connect to RPC server: %w", err))
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// unary call.
|
||||
func addMetadataUnaryInterceptor(
|
||||
md map[string]string) grpc.UnaryClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, method string, req, reply interface{},
|
||||
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
|
||||
opts ...grpc.CallOption) error {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return invoker(outCtx, method, req, reply, cc, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
|
||||
// appends any key-value metadata strings to the outgoing context of a grpc
|
||||
// stream call.
|
||||
func addMetaDataStreamInterceptor(
|
||||
md map[string]string) grpc.StreamClientInterceptor {
|
||||
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
|
||||
outCtx := contextWithMetadata(ctx, md)
|
||||
return streamer(outCtx, desc, cc, method, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// contextWithMetaData appends the given metadata key-value pairs to the given
|
||||
// context.
|
||||
func contextWithMetadata(ctx context.Context,
|
||||
md map[string]string) context.Context {
|
||||
|
||||
kvPairs := make([]string, 0, 2*len(md))
|
||||
for k, v := range md {
|
||||
kvPairs = append(kvPairs, k, v)
|
||||
}
|
||||
|
||||
return metadata.AppendToOutgoingContext(ctx, kvPairs...)
|
||||
}
|
||||
|
||||
// extractPathArgs parses the TLS certificate and macaroon paths from the
|
||||
// command.
|
||||
func extractPathArgs(ctx *cli.Context) (string, string, error) {
|
||||
network := strings.ToLower(ctx.GlobalString("network"))
|
||||
switch network {
|
||||
case "mainnet", "testnet", "regtest", "simnet", "signet":
|
||||
default:
|
||||
return "", "", fmt.Errorf("unknown network: %v", network)
|
||||
}
|
||||
|
||||
// We'll now fetch the lnddir so we can make a decision on how to
|
||||
// properly read the macaroons (if needed) and also the cert. This will
|
||||
// either be the default, or will have been overwritten by the end
|
||||
// user.
|
||||
lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir"))
|
||||
|
||||
// If the macaroon path as been manually provided, then we'll only
|
||||
// target the specified file.
|
||||
var macPath string
|
||||
if ctx.GlobalString("macaroonpath") != "" {
|
||||
macPath = lncfg.CleanAndExpandPath(ctx.GlobalString("macaroonpath"))
|
||||
} else {
|
||||
// Otherwise, we'll go into the path:
|
||||
// lnddir/data/chain/<chain>/<network> in order to fetch the
|
||||
// macaroon that we need.
|
||||
macPath = filepath.Join(
|
||||
lndDir, defaultDataDir, defaultChainSubDir,
|
||||
lnd.BitcoinChainName, network, defaultMacaroonFilename,
|
||||
)
|
||||
}
|
||||
|
||||
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath"))
|
||||
|
||||
// If a custom lnd directory was set, we'll also check if custom paths
|
||||
// for the TLS cert and macaroon file were set as well. If not, we'll
|
||||
// override their paths so they can be found within the custom lnd
|
||||
// directory set. This allows us to set a custom lnd directory, along
|
||||
// with custom paths to the TLS cert and macaroon file.
|
||||
if lndDir != defaultLndDir {
|
||||
tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
|
||||
}
|
||||
|
||||
return tlsCertPath, macPath, nil
|
||||
}
|
||||
|
||||
// checkNotBothSet accepts two flag names, a and b, and checks that only flag a
|
||||
// or flag b can be set, but not both. It returns the name of the flag or an
|
||||
// error.
|
||||
func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) {
|
||||
if ctx.IsSet(a) && ctx.IsSet(b) {
|
||||
return "", fmt.Errorf(
|
||||
"either %s or %s should be set, but not both", a, b,
|
||||
)
|
||||
}
|
||||
|
||||
if ctx.IsSet(a) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
import "github.com/lightningnetwork/lnd/cmd/commands"
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "lncli"
|
||||
app.Version = build.Version() + " commit=" + build.Commit
|
||||
app.Usage = "control plane for your Lightning Network Daemon (lnd)"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "rpcserver",
|
||||
Value: defaultRPCHostPort,
|
||||
Usage: "The host:port of LN daemon.",
|
||||
EnvVar: envVarRPCServer,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "lnddir",
|
||||
Value: defaultLndDir,
|
||||
Usage: "The path to lnd's base directory.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarLNDDir,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "socksproxy",
|
||||
Usage: "The host:port of a SOCKS proxy through " +
|
||||
"which all connections to the LN " +
|
||||
"daemon will be established over.",
|
||||
EnvVar: envVarSOCKSProxy,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tlscertpath",
|
||||
Value: defaultTLSCertPath,
|
||||
Usage: "The path to lnd's TLS certificate.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarTLSCertPath,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "chain, c",
|
||||
Usage: "The chain lnd is running on, e.g. bitcoin.",
|
||||
Value: "bitcoin",
|
||||
EnvVar: envVarChain,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "network, n",
|
||||
Usage: "The network lnd is running on, e.g. mainnet, " +
|
||||
"testnet, etc.",
|
||||
Value: "mainnet",
|
||||
EnvVar: envVarNetwork,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-macaroons",
|
||||
Usage: "Disable macaroon authentication.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macaroonpath",
|
||||
Usage: "The path to macaroon file.",
|
||||
TakesFile: true,
|
||||
EnvVar: envVarMacaroonPath,
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "macaroontimeout",
|
||||
Value: 60,
|
||||
Usage: "Anti-replay macaroon validity time in " +
|
||||
"seconds.",
|
||||
EnvVar: envVarMacaroonTimeout,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macaroonip",
|
||||
Usage: "If set, lock macaroon to specific IP address.",
|
||||
EnvVar: envVarMacaroonIP,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "profile, p",
|
||||
Usage: "Instead of reading settings from command " +
|
||||
"line parameters or using the default " +
|
||||
"profile, use a specific profile. If " +
|
||||
"a default profile is set, this flag can be " +
|
||||
"set to an empty string to disable reading " +
|
||||
"values from the profiles file.",
|
||||
EnvVar: envVarProfile,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "macfromjar",
|
||||
Usage: "Use this macaroon from the profile's " +
|
||||
"macaroon jar instead of the default one. " +
|
||||
"Can only be used if profiles are defined.",
|
||||
EnvVar: envVarMacFromJar,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "metadata",
|
||||
Usage: "This flag can be used to specify a key-value " +
|
||||
"pair that should be appended to the " +
|
||||
"outgoing context before the request is sent " +
|
||||
"to lnd. This flag may be specified multiple " +
|
||||
"times. The format is: \"key:value\".",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Usage: "Connect to the rpc server without TLS " +
|
||||
"authentication",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
createCommand,
|
||||
createWatchOnlyCommand,
|
||||
unlockCommand,
|
||||
changePasswordCommand,
|
||||
newAddressCommand,
|
||||
estimateFeeCommand,
|
||||
sendManyCommand,
|
||||
sendCoinsCommand,
|
||||
listUnspentCommand,
|
||||
connectCommand,
|
||||
disconnectCommand,
|
||||
openChannelCommand,
|
||||
batchOpenChannelCommand,
|
||||
closeChannelCommand,
|
||||
closeAllChannelsCommand,
|
||||
abandonChannelCommand,
|
||||
listPeersCommand,
|
||||
walletBalanceCommand,
|
||||
channelBalanceCommand,
|
||||
getInfoCommand,
|
||||
getDebugInfoCommand,
|
||||
encryptDebugPackageCommand,
|
||||
decryptDebugPackageCommand,
|
||||
getRecoveryInfoCommand,
|
||||
pendingChannelsCommand,
|
||||
sendPaymentCommand,
|
||||
payInvoiceCommand,
|
||||
sendToRouteCommand,
|
||||
addInvoiceCommand,
|
||||
lookupInvoiceCommand,
|
||||
listInvoicesCommand,
|
||||
listChannelsCommand,
|
||||
closedChannelsCommand,
|
||||
listPaymentsCommand,
|
||||
describeGraphCommand,
|
||||
getNodeMetricsCommand,
|
||||
getChanInfoCommand,
|
||||
getNodeInfoCommand,
|
||||
queryRoutesCommand,
|
||||
getNetworkInfoCommand,
|
||||
debugLevelCommand,
|
||||
decodePayReqCommand,
|
||||
listChainTxnsCommand,
|
||||
stopCommand,
|
||||
signMessageCommand,
|
||||
verifyMessageCommand,
|
||||
feeReportCommand,
|
||||
updateChannelPolicyCommand,
|
||||
forwardingHistoryCommand,
|
||||
exportChanBackupCommand,
|
||||
verifyChanBackupCommand,
|
||||
restoreChanBackupCommand,
|
||||
bakeMacaroonCommand,
|
||||
listMacaroonIDsCommand,
|
||||
deleteMacaroonIDCommand,
|
||||
listPermissionsCommand,
|
||||
printMacaroonCommand,
|
||||
constrainMacaroonCommand,
|
||||
trackPaymentCommand,
|
||||
versionCommand,
|
||||
profileSubCommand,
|
||||
getStateCommand,
|
||||
deletePaymentsCommand,
|
||||
sendCustomCommand,
|
||||
subscribeCustomCommand,
|
||||
fishCompletionCommand,
|
||||
listAliasesCommand,
|
||||
estimateRouteFeeCommand,
|
||||
generateManPageCommand,
|
||||
}
|
||||
|
||||
// Add any extra commands determined by build flags.
|
||||
app.Commands = append(app.Commands, autopilotCommands()...)
|
||||
app.Commands = append(app.Commands, invoicesCommands()...)
|
||||
app.Commands = append(app.Commands, neutrinoCommands()...)
|
||||
app.Commands = append(app.Commands, routerCommands()...)
|
||||
app.Commands = append(app.Commands, walletCommands()...)
|
||||
app.Commands = append(app.Commands, watchtowerCommands()...)
|
||||
app.Commands = append(app.Commands, wtclientCommands()...)
|
||||
app.Commands = append(app.Commands, devCommands()...)
|
||||
app.Commands = append(app.Commands, peersCommands()...)
|
||||
app.Commands = append(app.Commands, chainCommands()...)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// readPassword reads a password from the terminal. This requires there to be an
|
||||
// actual TTY so passing in a password from stdin won't work.
|
||||
func readPassword(text string) ([]byte, error) {
|
||||
fmt.Print(text)
|
||||
|
||||
// The variable syscall.Stdin is of a different type in the Windows API
|
||||
// that's why we need the explicit cast. And of course the linter
|
||||
// doesn't like it either.
|
||||
pw, err := term.ReadPassword(int(syscall.Stdin)) // nolint:unconvert
|
||||
fmt.Println()
|
||||
return pw, err
|
||||
}
|
||||
|
||||
// networkParams parses the global network flag into a chaincfg.Params.
|
||||
func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
|
||||
network := strings.ToLower(ctx.GlobalString("network"))
|
||||
switch network {
|
||||
case "mainnet":
|
||||
return &chaincfg.MainNetParams, nil
|
||||
|
||||
case "testnet":
|
||||
return &chaincfg.TestNet3Params, nil
|
||||
|
||||
case "regtest":
|
||||
return &chaincfg.RegressionNetParams, nil
|
||||
|
||||
case "simnet":
|
||||
return &chaincfg.SimNetParams, nil
|
||||
|
||||
case "signet":
|
||||
return &chaincfg.SigNetParams, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown network: %v", network)
|
||||
}
|
||||
}
|
||||
|
||||
// parseCoinSelectionStrategy parses a coin selection strategy string
|
||||
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
|
||||
func parseCoinSelectionStrategy(ctx *cli.Context) (
|
||||
lnrpc.CoinSelectionStrategy, error) {
|
||||
|
||||
strategy := ctx.String(coinSelectionStrategyFlag.Name)
|
||||
if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
|
||||
nil
|
||||
}
|
||||
|
||||
switch strategy {
|
||||
case "global-config":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
|
||||
nil
|
||||
|
||||
case "largest":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
|
||||
|
||||
case "random":
|
||||
return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown coin selection strategy "+
|
||||
"%v", strategy)
|
||||
}
|
||||
commands.Main()
|
||||
}
|
||||
|
@ -299,6 +299,8 @@ type InitFundingMsg struct {
|
||||
// channel that will be useful to our future selves.
|
||||
Memo []byte
|
||||
|
||||
CustomChannelData []byte
|
||||
|
||||
// Updates is a channel which updates to the opening status of the
|
||||
// channel are sent on.
|
||||
Updates chan *lnrpc.OpenStatusUpdate
|
||||
@ -4697,6 +4699,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
||||
ScidAliasFeature: scidFeatureVal,
|
||||
Memo: msg.Memo,
|
||||
TapscriptRoot: tapscriptRoot,
|
||||
CustomChannelData: msg.CustomChannelData,
|
||||
}
|
||||
|
||||
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
|
||||
|
@ -4684,7 +4684,8 @@ type Channel struct {
|
||||
// An optional note-to-self to go along with the channel containing some
|
||||
// useful information. This is only ever stored locally and in no way impacts
|
||||
// the channel's operation.
|
||||
Memo string `protobuf:"bytes,36,opt,name=memo,proto3" json:"memo,omitempty"`
|
||||
Memo string `protobuf:"bytes,36,opt,name=memo,proto3" json:"memo,omitempty"`
|
||||
CustomChannelData []byte `protobuf:"bytes,37,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Channel) Reset() {
|
||||
@ -4975,6 +4976,13 @@ func (x *Channel) GetMemo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Channel) GetCustomChannelData() []byte {
|
||||
if x != nil {
|
||||
return x.CustomChannelData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListChannelsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -7660,7 +7668,8 @@ type OpenChannelRequest struct {
|
||||
// the channel's operation.
|
||||
Memo string `protobuf:"bytes,27,opt,name=memo,proto3" json:"memo,omitempty"`
|
||||
// A list of selected outpoints that are allocated for channel funding.
|
||||
Outpoints []*OutPoint `protobuf:"bytes,28,rep,name=outpoints,proto3" json:"outpoints,omitempty"`
|
||||
Outpoints []*OutPoint `protobuf:"bytes,28,rep,name=outpoints,proto3" json:"outpoints,omitempty"`
|
||||
CustomChannelData []byte `protobuf:"bytes,29,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *OpenChannelRequest) Reset() {
|
||||
@ -7893,6 +7902,13 @@ func (x *OpenChannelRequest) GetOutpoints() []*OutPoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *OpenChannelRequest) GetCustomChannelData() []byte {
|
||||
if x != nil {
|
||||
return x.CustomChannelData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OpenStatusUpdate struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -18415,7 +18431,7 @@ var file_lightning_proto_rawDesc = []byte{
|
||||
0x73, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63,
|
||||
0x73, 0x22, 0xad, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x16, 0x0a,
|
||||
0x73, 0x22, 0xdd, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61,
|
||||
0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f,
|
||||
0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65,
|
||||
@ -18506,7 +18522,10 @@ var file_lightning_proto_rawDesc = []byte{
|
||||
0x6c, 0x69, 0x61, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0d,
|
||||
0x70, 0x65, 0x65, 0x72, 0x53, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d,
|
||||
0x6f, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
|
||||
0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e,
|
||||
0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11,
|
||||
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74,
|
||||
0x61, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
|
||||
0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74,
|
||||
0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
|
||||
0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e,
|
||||
@ -18905,7 +18924,7 @@ var file_lightning_proto_rawDesc = []byte{
|
||||
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64,
|
||||
0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xcb, 0x08, 0x0a, 0x12,
|
||||
0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xfb, 0x08, 0x0a, 0x12,
|
||||
0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62,
|
||||
0x79, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65,
|
||||
@ -18974,7 +18993,10 @@ var file_lightning_proto_rawDesc = []byte{
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75,
|
||||
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09,
|
||||
0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70,
|
||||
0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73,
|
||||
0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61,
|
||||
0x18, 0x1d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68,
|
||||
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70,
|
||||
0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39,
|
||||
0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e,
|
||||
|
@ -1590,6 +1590,8 @@ message Channel {
|
||||
the channel's operation.
|
||||
*/
|
||||
string memo = 36;
|
||||
|
||||
bytes custom_channel_data = 37;
|
||||
}
|
||||
|
||||
message ListChannelsRequest {
|
||||
@ -2426,6 +2428,8 @@ message OpenChannelRequest {
|
||||
A list of selected outpoints that are allocated for channel funding.
|
||||
*/
|
||||
repeated OutPoint outpoints = 28;
|
||||
|
||||
bytes custom_channel_data = 29;
|
||||
}
|
||||
message OpenStatusUpdate {
|
||||
oneof update {
|
||||
|
@ -3805,6 +3805,10 @@
|
||||
"memo": {
|
||||
"type": "string",
|
||||
"description": "An optional note-to-self to go along with the channel containing some\nuseful information. This is only ever stored locally and in no way impacts\nthe channel's operation."
|
||||
},
|
||||
"custom_channel_data": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6122,6 +6126,10 @@
|
||||
"$ref": "#/definitions/lnrpcOutPoint"
|
||||
},
|
||||
"description": "A list of selected outpoints that are allocated for channel funding."
|
||||
},
|
||||
"custom_channel_data": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -229,6 +229,8 @@ type InitFundingReserveMsg struct {
|
||||
// channel that will be useful to our future selves.
|
||||
Memo []byte
|
||||
|
||||
CustomChannelData []byte
|
||||
|
||||
// TapscriptRoot is an optional tapscript root that if provided, will
|
||||
// be used to create the combined key for musig2 based channels.
|
||||
TapscriptRoot fn.Option[chainhash.Hash]
|
||||
|
@ -2282,6 +2282,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||
FundUpToMaxAmt: fundUpToMaxAmt,
|
||||
MinFundAmt: minFundAmt,
|
||||
Memo: []byte(in.Memo),
|
||||
CustomChannelData: in.CustomChannelData,
|
||||
Outpoints: outpoints,
|
||||
}, nil
|
||||
}
|
||||
@ -4399,6 +4400,11 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
|
||||
// is returned and peerScidAlias will be an empty ShortChannelID.
|
||||
peerScidAlias, _ := r.server.aliasMgr.GetPeerAlias(chanID)
|
||||
|
||||
var customChannelData []byte
|
||||
dbChannel.CustomBlob.WhenSome(func(blob tlv.Blob) {
|
||||
customChannelData = blob
|
||||
})
|
||||
|
||||
channel := &lnrpc.Channel{
|
||||
Active: isActive,
|
||||
Private: isPrivate(dbChannel),
|
||||
@ -4431,6 +4437,7 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
|
||||
ZeroConf: dbChannel.IsZeroConf(),
|
||||
ZeroConfConfirmedScid: dbChannel.ZeroConfRealScid().ToUint64(),
|
||||
Memo: string(dbChannel.Memo),
|
||||
CustomChannelData: customChannelData,
|
||||
// TODO: remove the following deprecated fields
|
||||
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
|
||||
LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve),
|
||||
|
Loading…
x
Reference in New Issue
Block a user