2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-13 13:10:30 +00:00
loop/loopd/run.go

270 lines
7.4 KiB
Go
Raw Normal View History

package loopd
2019-03-06 20:13:50 +00:00
import (
2020-01-03 13:01:32 +00:00
"context"
"crypto/tls"
2019-03-06 20:13:50 +00:00
"fmt"
"net"
2019-03-06 20:13:50 +00:00
"os"
2019-03-07 10:37:28 +00:00
"path/filepath"
"strings"
2019-03-06 20:13:50 +00:00
2019-10-28 16:06:07 +00:00
"github.com/jessevdk/go-flags"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
2019-10-28 16:06:07 +00:00
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/signal"
2019-03-06 20:13:50 +00:00
)
const defaultConfigFilename = "loopd.conf"
2019-03-06 20:13:50 +00:00
var (
// LoopMinRequiredLndVersion is the minimum required version of lnd that
// is compatible with the current version of the loop client. Also all
// listed build tags/subservers need to be enabled.
LoopMinRequiredLndVersion = &verrpc.Version{
AppMajor: 0,
AppMinor: 11,
AppPatch: 1,
BuildTags: []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
},
}
)
// 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
2020-01-03 13:01:32 +00:00
// 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.
2020-05-15 10:17:57 +00:00
func newListenerCfg(config *Config, rpcCfg RPCConfig) *listenerCfg {
return &listenerCfg{
grpcListener: func(tlsCfg *tls.Config) (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
}
listener, err := net.Listen("tcp", config.RPCListen)
if err != nil {
return nil, err
}
return tls.NewListener(listener, tlsCfg), nil
},
restListener: func(tlsCfg *tls.Config) (net.Listener, error) {
// If a custom RPC listener is set, we disable REST.
if rpcCfg.RPCListener != nil {
return nil, nil
}
listener, err := net.Listen("tcp", config.RESTListen)
if err != nil {
return nil, err
}
return tls.NewListener(listener, tlsCfg), nil
},
getLnd: func(network lndclient.Network, cfg *lndConfig) (
*lndclient.GrpcLndServices, error) {
callerCtx, cancel := context.WithCancel(
context.Background(),
)
defer cancel()
svcCfg := &lndclient.LndServicesConfig{
LndAddress: cfg.Host,
Network: network,
CustomMacaroonPath: cfg.MacaroonPath,
TLSPath: cfg.TLSPath,
CheckVersion: LoopMinRequiredLndVersion,
BlockUntilChainSynced: true,
CallerCtx: callerCtx,
BlockUntilUnlocked: true,
}
2020-01-03 13:01:32 +00:00
// If a custom lnd connection is specified we use that
// directly.
if rpcCfg.LndConn != nil {
svcCfg.Dialer = func(context.Context, string) (
2020-01-03 13:01:32 +00:00
net.Conn, error) {
return rpcCfg.LndConn, nil
}
}
// Before we try to get our client connection, setup
// a goroutine which will cancel our lndclient if loopd
// is terminated, or exit if our context is cancelled.
go func() {
select {
// If the client decides to kill loop before
// lnd is synced, we cancel our context, which
// will unblock lndclient.
case <-interceptor.ShutdownChannel():
cancel()
// If our sync context was cancelled, we know
// that the function exited, which means that
// our client synced.
case <-callerCtx.Done():
}
}()
// This will block until lnd is synced to chain.
return lndclient.NewLndServices(svcCfg)
},
}
}
// Run starts the loop daemon and blocks until it's shut down again.
func Run(rpcCfg RPCConfig) error {
2020-05-15 10:17:55 +00:00
config := DefaultConfig()
2019-03-07 10:37:28 +00:00
// 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
2019-03-06 20:13:50 +00:00
}
2019-03-07 10:37:28 +00:00
if err != nil {
return err
}
2019-03-06 20:13:50 +00:00
2019-03-07 10:37:28 +00:00
// Parse ini file.
loopDir := lncfg.CleanAndExpandPath(config.LoopDir)
configFile := getConfigPath(config, loopDir)
2019-03-07 10:37:28 +00:00
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()
2019-03-06 20:13:50 +00:00
if err != nil {
2019-03-07 10:37:28 +00:00
return err
2019-03-06 20:13:50 +00:00
}
2019-03-07 10:37:28 +00:00
// 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)
}
2019-10-28 16:06:07 +00:00
// Special show command to list supported subsystems and exit.
if config.DebugLevel == "show" {
fmt.Printf("Supported subsystems: %v\n",
logWriter.SupportedSubsystems())
os.Exit(0)
}
// Validate our config before we proceed.
if err := Validate(&config); err != nil {
return err
}
2019-10-28 16:06:07 +00:00
// Start listening for signal interrupts regardless of which command
// we are running. When our command tries to get a lnd connection, it
// blocks until lnd is synced. We listen for interrupts so that we can
// shutdown the daemon while waiting for sync to complete.
shutdownInterceptor, err := signal.Intercept()
if err != nil {
return err
}
2019-10-28 16:06:07 +00:00
// Initialize logging at the default logging level.
logWriter := build.NewRotatingLogWriter()
SetupLoggers(logWriter, shutdownInterceptor)
2019-10-28 16:06:07 +00:00
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.
2019-10-28 16:06:07 +00:00
log.Infof("Version: %v", loop.Version())
lisCfg := newListenerCfg(&config, rpcCfg)
2019-03-07 10:37:28 +00:00
// Execute command.
if parser.Active == nil {
2020-05-15 10:17:58 +00:00
daemon := New(&config, lisCfg)
if err := daemon.Start(); err != nil {
return err
}
select {
case <-interceptor.ShutdownChannel():
log.Infof("Received SIGINT (Ctrl+C).")
daemon.Stop()
// The above stop will return immediately. But we'll be
// notified on the error channel once the process is
// complete.
return <-daemon.ErrChan
case err := <-daemon.ErrChan:
return err
}
2019-03-07 10:37:28 +00:00
}
if parser.Active.Name == "view" {
return view(&config, lisCfg)
2019-03-07 10:37:28 +00:00
}
return fmt.Errorf("unimplemented command %v", parser.Active.Name)
2019-03-06 20:13:50 +00:00
}
// getConfigPath gets our config path based on the values that are set in our
// config.
func getConfigPath(cfg Config, loopDir string) string {
// If the config file path provided by the user is set, then we just
// use this value.
if cfg.ConfigFile != defaultConfigFile {
return lncfg.CleanAndExpandPath(cfg.ConfigFile)
}
// If the user has set a loop directory that is different to the default
// we will use this loop directory as the location of our config file.
// We do not namespace by network, because this is a custom loop dir.
if loopDir != LoopDirBase {
return filepath.Join(loopDir, defaultConfigFilename)
}
// Otherwise, we are using our default loop directory, and the user did
// not set a config file path. We use our default loop dir, namespaced
// by network.
return filepath.Join(loopDir, cfg.Network, defaultConfigFilename)
}