From 37b24d59dd0131fb14bc358e6d82c285143814b3 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 31 Oct 2022 13:26:36 +0200 Subject: [PATCH] multi: split out LND connection from rpcProxy Remove the responsibility of creating an LND connection from the rpcProxy and instead let the main LightningTerminal struct handle it. All the lnd-connection specific functions are also moved into their own file. --- lnd_connection.go | 85 ++++++++++++++++++++++++++++++++++++++++++ rpc_proxy.go | 94 ++--------------------------------------------- terminal.go | 18 ++++++++- 3 files changed, 104 insertions(+), 93 deletions(-) create mode 100644 lnd_connection.go diff --git a/lnd_connection.go b/lnd_connection.go new file mode 100644 index 00000000..0ad9ca6c --- /dev/null +++ b/lnd_connection.go @@ -0,0 +1,85 @@ +package terminal + +import ( + "context" + "crypto/tls" + "fmt" + "net" + + grpcProxy "github.com/mwitkow/grpc-proxy/proxy" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/test/bufconn" +) + +// connectLND sets up LiT's LND connection. +func connectLND(cfg *Config, bufListener *bufconn.Listener) (*grpc.ClientConn, + error) { + + if cfg.lndRemote { + host, _, tlsPath, _, _ := cfg.lndConnectParams() + return dialBackend("lnd", host, tlsPath) + } + + // If LND is running in integrated mode, then we use a bufconn to + // connect to lnd in integrated mode. + return dialBufConnBackend(bufListener) +} + +// dialBackend connects to a gRPC backend through the given address and uses the +// given TLS certificate to authenticate the connection. +func dialBackend(name, dialAddr, tlsCertPath string) (*grpc.ClientConn, error) { + tlsConfig, err := credentials.NewClientTLSFromFile(tlsCertPath, "") + if err != nil { + return nil, fmt.Errorf("could not read %s TLS cert %s: %v", + name, tlsCertPath, err) + } + + opts := []grpc.DialOption{ + // From the grpcProxy doc: This codec is *crucial* to the + // functioning of the proxy. + grpc.WithCodec(grpcProxy.Codec()), // nolint + grpc.WithTransportCredentials(tlsConfig), + grpc.WithDefaultCallOptions(maxMsgRecvSize), + grpc.WithConnectParams(grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: defaultConnectTimeout, + }), + } + + log.Infof("Dialing %s gRPC server at %s", name, dialAddr) + cc, err := grpc.Dial(dialAddr, opts...) + if err != nil { + return nil, fmt.Errorf("failed dialing %s backend: %v", name, + err) + } + return cc, nil +} + +// dialBufConnBackend dials an in-memory connection to an RPC listener and +// ignores any TLS certificate mismatches. +func dialBufConnBackend(listener *bufconn.Listener) (*grpc.ClientConn, error) { + tlsConfig := credentials.NewTLS(&tls.Config{ + InsecureSkipVerify: true, + }) + + opts := []grpc.DialOption{ + grpc.WithContextDialer( + func(context.Context, string) (net.Conn, error) { + return listener.Dial() + }, + ), + // From the grpcProxy doc: This codec is *crucial* to the + // functioning of the proxy. + grpc.WithCodec(grpcProxy.Codec()), // nolint + grpc.WithTransportCredentials(tlsConfig), + grpc.WithDefaultCallOptions(maxMsgRecvSize), + grpc.WithConnectParams(grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: defaultConnectTimeout, + }), + } + + return grpc.Dial("", opts...) +} diff --git a/rpc_proxy.go b/rpc_proxy.go index 53cea101..0ac7ac90 100644 --- a/rpc_proxy.go +++ b/rpc_proxy.go @@ -2,12 +2,10 @@ package terminal import ( "context" - "crypto/tls" "encoding/base64" "encoding/hex" "fmt" "io/ioutil" - "net" "net/http" "strings" "sync/atomic" @@ -21,12 +19,9 @@ import ( "github.com/lightningnetwork/lnd/macaroons" grpcProxy "github.com/mwitkow/grpc-proxy/proxy" "google.golang.org/grpc" - "google.golang.org/grpc/backoff" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "google.golang.org/grpc/test/bufconn" "gopkg.in/macaroon.v2" ) @@ -65,7 +60,7 @@ func (e *proxyErr) Unwrap() error { // component. func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, superMacValidator session.SuperMacaroonValidator, - permsMgr *perms.Manager, bufListener *bufconn.Listener) *rpcProxy { + permsMgr *perms.Manager) *rpcProxy { // The gRPC web calls are protected by HTTP basic auth which is defined // by base64(username:password). Because we only have a password, we @@ -85,7 +80,6 @@ func newRpcProxy(cfg *Config, validator macaroons.MacaroonValidator, permsMgr: permsMgr, macValidator: validator, superMacValidator: superMacValidator, - bufListener: bufListener, } p.grpcServer = grpc.NewServer( // From the grpxProxy doc: This codec is *crucial* to the @@ -162,7 +156,6 @@ type rpcProxy struct { macValidator macaroons.MacaroonValidator superMacValidator session.SuperMacaroonValidator - bufListener *bufconn.Listener superMacaroon string @@ -176,21 +169,9 @@ type rpcProxy struct { } // Start creates initial connection to lnd. -func (p *rpcProxy) Start() error { +func (p *rpcProxy) Start(lndConn *grpc.ClientConn) error { var err error - - // Setup the connection to lnd. - host, _, tlsPath, _, _ := p.cfg.lndConnectParams() - - // We use a bufconn to connect to lnd in integrated mode. - if p.cfg.LndMode == ModeIntegrated { - p.lndConn, err = dialBufConnBackend(p.bufListener) - } else { - p.lndConn, err = dialBackend("lnd", host, tlsPath) - } - if err != nil { - return fmt.Errorf("could not dial lnd: %v", err) - } + p.lndConn = lndConn // Make sure we can connect to all the daemons that are configured to be // running in remote mode. @@ -242,13 +223,6 @@ func (p *rpcProxy) hasStarted() bool { func (p *rpcProxy) Stop() error { p.grpcServer.Stop() - if p.lndConn != nil { - if err := p.lndConn.Close(); err != nil { - log.Errorf("Error closing lnd connection: %v", err) - return err - } - } - if p.faradayConn != nil { if err := p.faradayConn.Close(); err != nil { log.Errorf("Error closing faraday connection: %v", err) @@ -681,68 +655,6 @@ func (p *rpcProxy) convertSuperMacaroon(ctx context.Context, macHex string, return nil, nil } -// dialBufConnBackend dials an in-memory connection to an RPC listener and -// ignores any TLS certificate mismatches. -func dialBufConnBackend(listener *bufconn.Listener) (*grpc.ClientConn, error) { - tlsConfig := credentials.NewTLS(&tls.Config{ - InsecureSkipVerify: true, - }) - conn, err := grpc.Dial( - "", - grpc.WithContextDialer( - func(context.Context, string) (net.Conn, error) { - return listener.Dial() - }, - ), - grpc.WithTransportCredentials(tlsConfig), - - // From the grpcProxy doc: This codec is *crucial* to the - // functioning of the proxy. - grpc.WithCodec(grpcProxy.Codec()), // nolint - grpc.WithTransportCredentials(tlsConfig), - grpc.WithDefaultCallOptions(maxMsgRecvSize), - grpc.WithConnectParams(grpc.ConnectParams{ - Backoff: backoff.DefaultConfig, - MinConnectTimeout: defaultConnectTimeout, - }), - ) - - return conn, err -} - -// dialBackend connects to a gRPC backend through the given address and uses the -// given TLS certificate to authenticate the connection. -func dialBackend(name, dialAddr, tlsCertPath string) (*grpc.ClientConn, error) { - var opts []grpc.DialOption - tlsConfig, err := credentials.NewClientTLSFromFile(tlsCertPath, "") - if err != nil { - return nil, fmt.Errorf("could not read %s TLS cert %s: %v", - name, tlsCertPath, err) - } - - opts = append( - opts, - - // From the grpcProxy doc: This codec is *crucial* to the - // functioning of the proxy. - grpc.WithCodec(grpcProxy.Codec()), // nolint - grpc.WithTransportCredentials(tlsConfig), - grpc.WithDefaultCallOptions(maxMsgRecvSize), - grpc.WithConnectParams(grpc.ConnectParams{ - Backoff: backoff.DefaultConfig, - MinConnectTimeout: defaultConnectTimeout, - }), - ) - - log.Infof("Dialing %s gRPC server at %s", name, dialAddr) - cc, err := grpc.Dial(dialAddr, opts...) - if err != nil { - return nil, fmt.Errorf("failed dialing %s backend: %v", name, - err) - } - return cc, nil -} - // readMacaroon tries to read the macaroon file at the specified path and create // gRPC dial options from it. func readMacaroon(macPath string) ([]byte, error) { diff --git a/terminal.go b/terminal.go index 4924fcde..8696b8b3 100644 --- a/terminal.go +++ b/terminal.go @@ -160,6 +160,7 @@ type LightningTerminal struct { wg sync.WaitGroup errQueue *queue.ConcurrentQueue[error] + lndConn *grpc.ClientConn lndClient *lndclient.GrpcLndServices basicClient lnrpc.LightningClient @@ -244,7 +245,7 @@ func (g *LightningTerminal) Run() error { g.loopServer = loopd.New(g.cfg.Loop, nil) g.poolServer = pool.NewServer(g.cfg.Pool) g.rpcProxy = newRpcProxy( - g.cfg, g, g.validateSuperMacaroon, g.permsMgr, bufRpcListener, + g.cfg, g, g.validateSuperMacaroon, g.permsMgr, ) g.accountService, err = accounts.NewService( filepath.Dir(g.cfg.MacaroonPath), g.errQueue.ChanIn(), @@ -436,11 +437,17 @@ func (g *LightningTerminal) Run() error { } }() + // Connect to LND. + g.lndConn, err = connectLND(g.cfg, bufRpcListener) + if err != nil { + return fmt.Errorf("could not connect to LND: %v", err) + } + // Now start the RPC proxy that will handle all incoming gRPC, grpc-web // and REST requests. We also start the main web server that dispatches // requests either to the static UI file server or the RPC proxy. This // makes it possible to unlock lnd through the UI. - if err := g.rpcProxy.Start(); err != nil { + if err := g.rpcProxy.Start(g.lndConn); err != nil { return fmt.Errorf("error starting lnd gRPC proxy server: %v", err) } @@ -1178,6 +1185,13 @@ func (g *LightningTerminal) shutdown() error { } } + if g.lndConn != nil { + if err := g.lndConn.Close(); err != nil { + log.Errorf("Error closing lnd connection: %v", err) + returnErr = err + } + } + if g.httpServer != nil { if err := g.httpServer.Close(); err != nil { log.Errorf("Error stopping UI server: %v", err)