diff --git a/.gitignore b/.gitignore index ec3842d..ad90636 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,6 @@ output*.log loop !cmd/loop/ -loopd -!cmd/loopd/ - *.key *.hex diff --git a/cmd/loopd/main.go b/cmd/loopd/main.go index 3be4345..0310198 100644 --- a/cmd/loopd/main.go +++ b/cmd/loopd/main.go @@ -2,118 +2,14 @@ package main import ( "fmt" - "os" - "path/filepath" - "strings" - "sync" - "github.com/jessevdk/go-flags" - "github.com/lightninglabs/loop" - "github.com/lightningnetwork/lnd/build" - "github.com/lightningnetwork/lnd/lntypes" -) - -const ( - defaultConfTarget = int32(6) -) - -var ( - defaultConfigFilename = "loopd.conf" - - swaps = make(map[lntypes.Hash]loop.SwapInfo) - subscribers = make(map[int]chan<- interface{}) - nextSubscriberID int - swapsLock sync.Mutex + "github.com/lightninglabs/loop/loopd" ) func main() { - err := start() + cfg := loopd.RPCConfig{} + err := loopd.Start(cfg) if err != nil { fmt.Println(err) } } - -func start() error { - config := defaultConfig - - // Parse command line flags. - parser := flags.NewParser(&config, flags.Default) - parser.SubcommandsOptional = true - - _, err := parser.Parse() - if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { - return nil - } - if err != nil { - return err - } - - // Parse ini file. - loopDir := filepath.Join(loopDirBase, config.Network) - if err := os.MkdirAll(loopDir, os.ModePerm); err != nil { - return err - } - - configFile := filepath.Join(loopDir, defaultConfigFilename) - if err := flags.IniParse(configFile, &config); err != nil { - // If it's a parsing related error, then we'll return - // immediately, otherwise we can proceed as possibly the config - // file doesn't exist which is OK. - if _, ok := err.(*flags.IniError); ok { - return err - } - } - - // Parse command line flags again to restore flags overwritten by ini - // parse. - _, err = parser.Parse() - if err != nil { - return err - } - - // Show the version and exit if the version flag was specified. - appName := filepath.Base(os.Args[0]) - appName = strings.TrimSuffix(appName, filepath.Ext(appName)) - if config.ShowVersion { - fmt.Println(appName, "version", loop.Version()) - os.Exit(0) - } - - // Special show command to list supported subsystems and exit. - if config.DebugLevel == "show" { - fmt.Printf("Supported subsystems: %v\n", - logWriter.SupportedSubsystems()) - os.Exit(0) - } - - // Append the network type to the log directory so it is - // "namespaced" per network in the same fashion as the data directory. - config.LogDir = filepath.Join(config.LogDir, config.Network) - - // Initialize logging at the default logging level. - err = logWriter.InitLogRotator( - filepath.Join(config.LogDir, defaultLogFilename), - config.MaxLogFileSize, config.MaxLogFiles, - ) - if err != nil { - return err - } - err = build.ParseAndSetDebugLevels(config.DebugLevel, logWriter) - if err != nil { - return err - } - - // Print the version before executing either primary directive. - log.Infof("Version: %v", loop.Version()) - - // Execute command. - if parser.Active == nil { - return daemon(&config) - } - - if parser.Active.Name == "view" { - return view(&config) - } - - return fmt.Errorf("unimplemented command %v", parser.Active.Name) -} diff --git a/go.mod b/go.mod index a0ad3d9..9a3bfd3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/grpc-ecosystem/grpc-gateway v1.10.0 github.com/jessevdk/go-flags v1.4.0 - github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191212054348-dca31c2bf8be + github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194 github.com/lightningnetwork/lnd/queue v1.0.2 github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect diff --git a/go.sum b/go.sum index c1dbd35..f77ece8 100644 --- a/go.sum +++ b/go.sum @@ -136,10 +136,11 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0= github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= -github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a h1:GoWPN4i4jTKRxhVNh9a2vvBBO1Y2seiJB+SopUYoKyo= -github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= -github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191212054348-dca31c2bf8be h1:CX8ScoYxJju6AcceHY6nATfRLThZpewqHcgn4sGMdZE= -github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191212054348-dca31c2bf8be/go.mod h1:60/zDjDYaYPmISAm3J1WWKyyt+ZiAvuET1XzglO8s70= +github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= +github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d h1:U50MHOOeL6gR3Ee/l0eMvZMpmRo+ydzmlQuIruCyCsA= +github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= +github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194 h1:PCzjJcVWcMbkiQvzFNc3ta0JmiMprFDqzMZsSpd/km8= +github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4= github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw= github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= diff --git a/lndclient/basic_client.go b/lndclient/basic_client.go index 917e9f1..f4f18c1 100644 --- a/lndclient/basic_client.go +++ b/lndclient/basic_client.go @@ -103,7 +103,7 @@ func NewBasicClient(lndHost, tlsPath, macDir, network string, basicOptions ...Ba // We need to use a custom dialer so we can also connect to unix sockets // and not just TCP addresses. opts = append( - opts, grpc.WithDialer( + opts, grpc.WithContextDialer( lncfg.ClientAddressDialer(defaultRPCPort), ), ) diff --git a/lndclient/lnd_services.go b/lndclient/lnd_services.go index a7b1678..c6009ac 100644 --- a/lndclient/lnd_services.go +++ b/lndclient/lnd_services.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "path/filepath" "time" @@ -38,9 +39,24 @@ type GrpcLndServices struct { cleanup func() } -// NewLndServices creates a set of required RPC services. -func NewLndServices(lndAddress, application, network, macaroonDir, - tlsPath string) (*GrpcLndServices, error) { +// 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) { // Based on the network, if the macaroon directory isn't set, then // we'll use the expected default locations. @@ -85,7 +101,7 @@ func NewLndServices(lndAddress, application, network, macaroonDir, // Setup connection with lnd log.Infof("Creating lnd connection to %v", lndAddress) - conn, err := getClientConn(lndAddress, network, tlsPath) + conn, err := getClientConn(dialer, lndAddress, tlsPath) if err != nil { return nil, err } @@ -189,7 +205,9 @@ var ( maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200) ) -func getClientConn(address string, network string, tlsPath string) ( +type dialerFunc func(context.Context, string) (net.Conn, error) + +func getClientConn(dialer dialerFunc, address string, tlsPath string) ( *grpc.ClientConn, error) { // Load the specified TLS certificate and build transport credentials @@ -206,15 +224,12 @@ func getClientConn(address string, network string, tlsPath string) ( // Create a dial options array. opts := []grpc.DialOption{ grpc.WithTransportCredentials(creds), + + // Use a custom dialer, to allow connections to unix sockets, + // in-memory listeners etc, and not just TCP addresses. + grpc.WithContextDialer(dialer), } - // We need to use a custom dialer so we can also connect to unix sockets - // and not just TCP addresses. - opts = append( - opts, grpc.WithDialer( - lncfg.ClientAddressDialer(defaultRPCPort), - ), - ) conn, err := grpc.Dial(address, opts...) if err != nil { return nil, fmt.Errorf("unable to connect to RPC server: %v", err) diff --git a/cmd/loopd/config.go b/loopd/config.go similarity index 99% rename from cmd/loopd/config.go rename to loopd/config.go index 690665b..fdeb496 100644 --- a/cmd/loopd/config.go +++ b/loopd/config.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "path/filepath" diff --git a/cmd/loopd/daemon.go b/loopd/daemon.go similarity index 75% rename from cmd/loopd/daemon.go rename to loopd/daemon.go index 11f0790..a72219e 100644 --- a/cmd/loopd/daemon.go +++ b/loopd/daemon.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "context" @@ -14,14 +14,27 @@ import ( proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/lndclient" "github.com/lightninglabs/loop/looprpc" "google.golang.org/grpc" ) +// listenerCfg holds closures used to retrieve listeners for the gRPC services. +type listenerCfg struct { + // grpcListener returns a listener to use for the gRPC server. + grpcListener func() (net.Listener, error) + + // restListener returns a listener to use for the REST proxy. + restListener func() (net.Listener, error) + + // getLnd returns a grpc connection to an lnd instance. + getLnd func(string, *lndConfig) (*lndclient.GrpcLndServices, error) +} + // daemon runs loopd in daemon mode. It will listen for grpc connections, // execute commands and pass back swap status information. -func daemon(config *config) error { - lnd, err := getLnd(config.Network, config.Lnd) +func daemon(config *config, lisCfg *listenerCfg) error { + lnd, err := lisCfg.getLnd(config.Network, config.Lnd) if err != nil { return err } @@ -74,7 +87,7 @@ func daemon(config *config) error { // Next, start the gRPC server listening for HTTP/2 connections. log.Infof("Starting gRPC listener") - grpcListener, err := net.Listen("tcp", config.RPCListen) + grpcListener, err := lisCfg.grpcListener() if err != nil { return fmt.Errorf("RPC server unable to listen on %s", config.RPCListen) @@ -95,15 +108,30 @@ func daemon(config *config) error { return err } - log.Infof("Starting REST proxy listener") - restListener, err := net.Listen("tcp", config.RESTListen) + restListener, err := lisCfg.restListener() if err != nil { return fmt.Errorf("REST proxy unable to listen on %s", config.RESTListen) } - defer restListener.Close() - proxy := &http.Server{Handler: mux} - go proxy.Serve(restListener) + + // A nil listener indicates REST is disabled. + if restListener != nil { + log.Infof("Starting REST proxy listener") + + defer restListener.Close() + proxy := &http.Server{Handler: mux} + + go func() { + err := proxy.Serve(restListener) + // ErrServerClosed is always returned when the proxy is + // shut down, so don't log it. + if err != nil && err != http.ErrServerClosed { + log.Error(err) + } + }() + } else { + log.Infof("REST proxy disabled") + } statusChan := make(chan loop.SwapInfo) @@ -161,7 +189,10 @@ func daemon(config *config) error { defer wg.Done() log.Infof("RPC server listening on %s", grpcListener.Addr()) - log.Infof("REST proxy listening on %s", restListener.Addr()) + + if restListener != nil { + log.Infof("REST proxy listening on %s", restListener.Addr()) + } err = grpcServer.Serve(grpcListener) if err != nil { diff --git a/cmd/loopd/log.go b/loopd/log.go similarity index 98% rename from cmd/loopd/log.go rename to loopd/log.go index d0ff029..dd1811a 100644 --- a/cmd/loopd/log.go +++ b/loopd/log.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "github.com/btcsuite/btclog" diff --git a/loopd/start.go b/loopd/start.go new file mode 100644 index 0000000..321ed5c --- /dev/null +++ b/loopd/start.go @@ -0,0 +1,177 @@ +package loopd + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/jessevdk/go-flags" + "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/lndclient" + "github.com/lightningnetwork/lnd/build" + "github.com/lightningnetwork/lnd/lntypes" +) + +const ( + defaultConfTarget = int32(6) +) + +var ( + defaultConfigFilename = "loopd.conf" + + swaps = make(map[lntypes.Hash]loop.SwapInfo) + subscribers = make(map[int]chan<- interface{}) + nextSubscriberID int + swapsLock sync.Mutex +) + +// RPCConfig holds optional options that can be used to make the loop daemon +// communicate on custom connections. +type RPCConfig struct { + // RPCListener is an optional listener that if set will override the + // daemon's gRPC settings, and make the gRPC server listen on this + // listener. + // Note that setting this will also disable REST. + RPCListener net.Listener + + // LndConn is an optional connection to an lnd instance. If set it will + // override the TCP connection created from daemon's config. + LndConn net.Conn +} + +// newListenerCfg creates and returns a new listenerCfg from the passed config +// and RPCConfig. +func newListenerCfg(config *config, rpcCfg RPCConfig) *listenerCfg { + return &listenerCfg{ + grpcListener: func() (net.Listener, error) { + // If a custom RPC listener is set, we will listen on + // it instead of the regular tcp socket. + if rpcCfg.RPCListener != nil { + return rpcCfg.RPCListener, nil + } + + return net.Listen("tcp", config.RPCListen) + }, + restListener: func() (net.Listener, error) { + // If a custom RPC listener is set, we disable REST. + if rpcCfg.RPCListener != nil { + return nil, nil + } + + return net.Listen("tcp", config.RESTListen) + }, + getLnd: func(network string, cfg *lndConfig) ( + *lndclient.GrpcLndServices, error) { + + // If a custom lnd connection is specified we use that + // directly. + if rpcCfg.LndConn != nil { + dialer := func(context.Context, string) ( + net.Conn, error) { + return rpcCfg.LndConn, nil + } + + return lndclient.NewLndServicesWithDialer( + dialer, + rpcCfg.LndConn.RemoteAddr().String(), + network, cfg.MacaroonDir, cfg.TLSPath, + ) + } + + return lndclient.NewLndServices( + cfg.Host, network, cfg.MacaroonDir, cfg.TLSPath, + ) + }, + } +} + +func Start(rpcCfg RPCConfig) error { + config := defaultConfig + + // Parse command line flags. + parser := flags.NewParser(&config, flags.Default) + parser.SubcommandsOptional = true + + _, err := parser.Parse() + if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { + return nil + } + if err != nil { + return err + } + + // Parse ini file. + loopDir := filepath.Join(loopDirBase, config.Network) + if err := os.MkdirAll(loopDir, os.ModePerm); err != nil { + return err + } + + configFile := filepath.Join(loopDir, defaultConfigFilename) + if err := flags.IniParse(configFile, &config); err != nil { + // If it's a parsing related error, then we'll return + // immediately, otherwise we can proceed as possibly the config + // file doesn't exist which is OK. + if _, ok := err.(*flags.IniError); ok { + return err + } + } + + // Parse command line flags again to restore flags overwritten by ini + // parse. + _, err = parser.Parse() + if err != nil { + return err + } + + // Show the version and exit if the version flag was specified. + appName := filepath.Base(os.Args[0]) + appName = strings.TrimSuffix(appName, filepath.Ext(appName)) + if config.ShowVersion { + fmt.Println(appName, "version", loop.Version()) + os.Exit(0) + } + + // Special show command to list supported subsystems and exit. + if config.DebugLevel == "show" { + fmt.Printf("Supported subsystems: %v\n", + logWriter.SupportedSubsystems()) + os.Exit(0) + } + + // Append the network type to the log directory so it is + // "namespaced" per network in the same fashion as the data directory. + config.LogDir = filepath.Join(config.LogDir, config.Network) + + // Initialize logging at the default logging level. + err = logWriter.InitLogRotator( + filepath.Join(config.LogDir, defaultLogFilename), + config.MaxLogFileSize, config.MaxLogFiles, + ) + if err != nil { + return err + } + err = build.ParseAndSetDebugLevels(config.DebugLevel, logWriter) + if err != nil { + return err + } + + // Print the version before executing either primary directive. + log.Infof("Version: %v", loop.Version()) + + lisCfg := newListenerCfg(&config, rpcCfg) + + // Execute command. + if parser.Active == nil { + return daemon(&config, lisCfg) + } + + if parser.Active.Name == "view" { + return view(&config, lisCfg) + } + + return fmt.Errorf("unimplemented command %v", parser.Active.Name) +} diff --git a/cmd/loopd/swapclient_server.go b/loopd/swapclient_server.go similarity index 99% rename from cmd/loopd/swapclient_server.go rename to loopd/swapclient_server.go index 4c1ae4d..232061f 100644 --- a/cmd/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "context" diff --git a/cmd/loopd/utils.go b/loopd/utils.go similarity index 75% rename from cmd/loopd/utils.go rename to loopd/utils.go index 601957e..9b66685 100644 --- a/cmd/loopd/utils.go +++ b/loopd/utils.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "os" @@ -8,13 +8,6 @@ import ( "github.com/lightninglabs/loop/lndclient" ) -// getLnd returns an instance of the lnd services proxy. -func getLnd(network string, cfg *lndConfig) (*lndclient.GrpcLndServices, error) { - return lndclient.NewLndServices( - cfg.Host, "client", network, cfg.MacaroonDir, cfg.TLSPath, - ) -} - // getClient returns an instance of the swap client. func getClient(network, swapServer string, insecure bool, tlsPathServer string, lnd *lndclient.LndServices) (*loop.Client, func(), error) { diff --git a/cmd/loopd/view.go b/loopd/view.go similarity index 95% rename from cmd/loopd/view.go rename to loopd/view.go index 50e3f4e..b9a6b30 100644 --- a/cmd/loopd/view.go +++ b/loopd/view.go @@ -1,4 +1,4 @@ -package main +package loopd import ( "fmt" @@ -11,13 +11,13 @@ import ( ) // view prints all swaps currently in the database. -func view(config *config) error { +func view(config *config, lisCfg *listenerCfg) error { chainParams, err := swap.ChainParamsFromNetwork(config.Network) if err != nil { return err } - lnd, err := getLnd(config.Network, config.Lnd) + lnd, err := lisCfg.getLnd(config.Network, config.Lnd) if err != nil { return err }