2019-03-06 20:13:50 +00:00
|
|
|
package lndclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-01-03 13:01:31 +00:00
|
|
|
"net"
|
2019-03-06 20:13:50 +00:00
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
"github.com/btcsuite/btcutil"
|
2019-03-07 02:22:46 +00:00
|
|
|
"github.com/lightninglabs/loop/swap"
|
2019-03-06 20:13:50 +00:00
|
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
)
|
|
|
|
|
|
|
|
var rpcTimeout = 30 * time.Second
|
|
|
|
|
|
|
|
// LndServices constitutes a set of required services.
|
|
|
|
type LndServices struct {
|
|
|
|
Client LightningClient
|
|
|
|
WalletKit WalletKitClient
|
|
|
|
ChainNotifier ChainNotifierClient
|
|
|
|
Signer SignerClient
|
|
|
|
Invoices InvoicesClient
|
2019-03-25 09:31:03 +00:00
|
|
|
Router RouterClient
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
ChainParams *chaincfg.Params
|
2019-04-17 02:15:10 +00:00
|
|
|
|
|
|
|
macaroons *macaroonPouch
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GrpcLndServices constitutes a set of required RPC services.
|
|
|
|
type GrpcLndServices struct {
|
|
|
|
LndServices
|
|
|
|
|
|
|
|
cleanup func()
|
|
|
|
}
|
|
|
|
|
2020-01-03 13:01:31 +00:00
|
|
|
// NewLndServices creates creates a connection to the given lnd instance and
|
|
|
|
// creates a set of required RPC services.
|
|
|
|
func NewLndServices(lndAddress, network, macaroonDir, tlsPath string) (
|
|
|
|
*GrpcLndServices, error) {
|
|
|
|
|
|
|
|
// We need to use a custom dialer so we can also connect to unix
|
|
|
|
// sockets and not just TCP addresses.
|
|
|
|
dialer := lncfg.ClientAddressDialer(defaultRPCPort)
|
|
|
|
|
|
|
|
return NewLndServicesWithDialer(
|
|
|
|
dialer, lndAddress, network, macaroonDir, tlsPath,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewLndServices creates a set of required RPC services by connecting to lnd
|
|
|
|
// using the given dialer.
|
|
|
|
func NewLndServicesWithDialer(dialer dialerFunc, lndAddress, network,
|
|
|
|
macaroonDir, tlsPath string) (*GrpcLndServices, error) {
|
2019-04-17 02:15:10 +00:00
|
|
|
|
2019-04-17 02:18:28 +00:00
|
|
|
// Based on the network, if the macaroon directory isn't set, then
|
|
|
|
// we'll use the expected default locations.
|
2019-04-17 02:15:10 +00:00
|
|
|
if macaroonDir == "" {
|
2019-04-17 02:18:28 +00:00
|
|
|
switch network {
|
|
|
|
case "testnet":
|
|
|
|
macaroonDir = filepath.Join(
|
|
|
|
defaultLndDir, defaultDataDir,
|
|
|
|
defaultChainSubDir, "bitcoin", "testnet",
|
|
|
|
)
|
|
|
|
|
|
|
|
case "mainnet":
|
|
|
|
macaroonDir = filepath.Join(
|
|
|
|
defaultLndDir, defaultDataDir,
|
|
|
|
defaultChainSubDir, "bitcoin", "mainnet",
|
|
|
|
)
|
|
|
|
|
|
|
|
case "simnet":
|
|
|
|
macaroonDir = filepath.Join(
|
|
|
|
defaultLndDir, defaultDataDir,
|
|
|
|
defaultChainSubDir, "bitcoin", "simnet",
|
|
|
|
)
|
|
|
|
|
|
|
|
case "regtest":
|
|
|
|
macaroonDir = filepath.Join(
|
|
|
|
defaultLndDir, defaultDataDir,
|
|
|
|
defaultChainSubDir, "bitcoin", "regtest",
|
|
|
|
)
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported network: %v",
|
|
|
|
network)
|
|
|
|
}
|
2019-04-17 02:15:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we've ensured our macaroon directory is set properly, we
|
|
|
|
// can retrieve our full macaroon pouch from the directory.
|
|
|
|
macaroons, err := newMacaroonPouch(macaroonDir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to obtain macaroons: %v", err)
|
|
|
|
}
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
// Setup connection with lnd
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Infof("Creating lnd connection to %v", lndAddress)
|
2020-01-03 13:01:31 +00:00
|
|
|
conn, err := getClientConn(dialer, lndAddress, tlsPath)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Infof("Connected to lnd")
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 02:22:46 +00:00
|
|
|
chainParams, err := swap.ChainParamsFromNetwork(network)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-17 02:17:19 +00:00
|
|
|
lightningClient := newLightningClient(
|
|
|
|
conn, chainParams, macaroons.adminMac,
|
|
|
|
)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-04-17 02:17:19 +00:00
|
|
|
// With our macaroons obtained, we'll ensure that the network for lnd
|
|
|
|
// matches our expected network.
|
2019-03-06 20:13:50 +00:00
|
|
|
info, err := lightningClient.GetInfo(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
2019-04-17 02:17:19 +00:00
|
|
|
return nil, fmt.Errorf("unable to get info for lnd "+
|
|
|
|
"node: %v", err)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
if network != info.Network {
|
|
|
|
conn.Close()
|
|
|
|
return nil, errors.New(
|
|
|
|
"network mismatch with connected lnd instance",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-04-17 02:17:19 +00:00
|
|
|
// With the network check passed, we'll now initialize the rest of the
|
2019-04-17 02:18:28 +00:00
|
|
|
// sub-server connections, giving each of them their specific macaroon.
|
2019-04-17 02:17:19 +00:00
|
|
|
notifierClient := newChainNotifierClient(conn, macaroons.chainMac)
|
|
|
|
signerClient := newSignerClient(conn, macaroons.signerMac)
|
|
|
|
walletKitClient := newWalletKitClient(conn, macaroons.walletKitMac)
|
|
|
|
invoicesClient := newInvoicesClient(conn, macaroons.invoiceMac)
|
2019-03-25 09:31:03 +00:00
|
|
|
routerClient := newRouterClient(conn, macaroons.routerMac)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
cleanup := func() {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Closing lnd connection")
|
2019-03-06 20:13:50 +00:00
|
|
|
conn.Close()
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Wait for client to finish")
|
2019-03-06 20:13:50 +00:00
|
|
|
lightningClient.WaitForFinished()
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Wait for chain notifier to finish")
|
2019-03-06 20:13:50 +00:00
|
|
|
notifierClient.WaitForFinished()
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Wait for invoices to finish")
|
2019-03-06 20:13:50 +00:00
|
|
|
invoicesClient.WaitForFinished()
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Lnd services finished")
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
services := &GrpcLndServices{
|
|
|
|
LndServices: LndServices{
|
|
|
|
Client: lightningClient,
|
|
|
|
WalletKit: walletKitClient,
|
|
|
|
ChainNotifier: notifierClient,
|
|
|
|
Signer: signerClient,
|
|
|
|
Invoices: invoicesClient,
|
2019-03-25 09:31:03 +00:00
|
|
|
Router: routerClient,
|
2019-03-06 20:13:50 +00:00
|
|
|
ChainParams: chainParams,
|
2019-04-17 02:15:10 +00:00
|
|
|
macaroons: macaroons,
|
2019-03-06 20:13:50 +00:00
|
|
|
},
|
|
|
|
cleanup: cleanup,
|
|
|
|
}
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Infof("Using network %v", network)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
return services, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the lnd connection and waits for all sub server clients to
|
|
|
|
// finish their goroutines.
|
|
|
|
func (s *GrpcLndServices) Close() {
|
|
|
|
s.cleanup()
|
|
|
|
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Debugf("Lnd services finished")
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
defaultRPCPort = "10009"
|
|
|
|
defaultLndDir = btcutil.AppDataDir("lnd", false)
|
|
|
|
defaultTLSCertFilename = "tls.cert"
|
2019-04-17 02:11:34 +00:00
|
|
|
defaultTLSCertPath = filepath.Join(
|
|
|
|
defaultLndDir, defaultTLSCertFilename,
|
|
|
|
)
|
|
|
|
defaultDataDir = "data"
|
|
|
|
defaultChainSubDir = "chain"
|
|
|
|
|
|
|
|
defaultAdminMacaroonFilename = "admin.macaroon"
|
|
|
|
defaultInvoiceMacaroonFilename = "invoices.macaroon"
|
|
|
|
defaultChainMacaroonFilename = "chainnotifier.macaroon"
|
|
|
|
defaultWalletKitMacaroonFilename = "walletkit.macaroon"
|
2019-03-25 09:31:03 +00:00
|
|
|
defaultRouterMacaroonFilename = "router.macaroon"
|
2019-04-17 02:11:34 +00:00
|
|
|
defaultSignerFilename = "signer.macaroon"
|
2019-05-21 23:57:04 +00:00
|
|
|
|
|
|
|
// maxMsgRecvSize is the largest gRPC message our client will receive.
|
2019-11-15 15:29:20 +00:00
|
|
|
// We set this to 200MiB.
|
|
|
|
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
|
2020-01-03 13:01:31 +00:00
|
|
|
type dialerFunc func(context.Context, string) (net.Conn, error)
|
|
|
|
|
|
|
|
func getClientConn(dialer dialerFunc, address string, tlsPath string) (
|
2019-03-06 20:13:50 +00:00
|
|
|
*grpc.ClientConn, error) {
|
|
|
|
|
|
|
|
// Load the specified TLS certificate and build transport credentials
|
|
|
|
// with it.
|
|
|
|
if tlsPath == "" {
|
|
|
|
tlsPath = defaultTLSCertPath
|
|
|
|
}
|
|
|
|
|
|
|
|
creds, err := credentials.NewClientTLSFromFile(tlsPath, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a dial options array.
|
|
|
|
opts := []grpc.DialOption{
|
|
|
|
grpc.WithTransportCredentials(creds),
|
2020-01-03 13:01:31 +00:00
|
|
|
|
|
|
|
// Use a custom dialer, to allow connections to unix sockets,
|
|
|
|
// in-memory listeners etc, and not just TCP addresses.
|
|
|
|
grpc.WithContextDialer(dialer),
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := grpc.Dial(address, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
}
|