diff --git a/Makefile b/Makefile index ccf95cb902..8fd0f197b1 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,7 @@ build: @$(call print, "Building debug lnd and lncli.") $(GOBUILD) -tags="$(DEV_TAGS)" -o lnd-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lnd $(GOBUILD) -tags="$(DEV_TAGS)" -o lncli-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/lncli + $(GOBUILD) -tags="$(DEV_TAGS)" -o multinode-debug $(DEV_GCFLAGS) $(DEV_LDFLAGS) $(PKG)/cmd/multinode #? build-itest: Build integration test binaries, place them in itest directory build-itest: @@ -136,6 +137,7 @@ install-binaries: @$(call print, "Installing lnd and lncli.") $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lnd $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/lncli + $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/multinode #? manpages: generate and install man pages manpages: diff --git a/cmd/multinode/main.go b/cmd/multinode/main.go new file mode 100644 index 0000000000..9842fefe3a --- /dev/null +++ b/cmd/multinode/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jessevdk/go-flags" + "github.com/lightningnetwork/lnd" + graphsources "github.com/lightningnetwork/lnd/graph/sources" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/signal" +) + +func main() { + // Hook interceptor for os signals. + shutdownInterceptor, err := signal.Intercept() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + lndProviders := setupGraphSourceNode(shutdownInterceptor) + setupDependentNode(shutdownInterceptor, lndProviders) + <-shutdownInterceptor.ShutdownChannel() +} +func setupGraphSourceNode(interceptor signal.Interceptor) lnd.Providers { + preCfg := graphConfig() + cfg, err := lnd.LoadConfigNoFlags(*preCfg, interceptor) + if err != nil { + os.Exit(1) + } + implCfg := cfg.ImplementationConfig(interceptor) + lndProviders := make(chan lnd.Providers, 1) + go func() { + if err := lnd.Main( + cfg, lnd.ListenerCfg{}, implCfg, interceptor, + lndProviders, + ); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + } + fmt.Println("Graph node has stopped") + os.Exit(1) + }() + return <-lndProviders +} +func setupDependentNode(interceptor signal.Interceptor, + lndProviders lnd.Providers) { + // Load the configuration, and parse any command line options. This + // function will also set up logging properly. + loadedConfig, err := lnd.LoadConfig(interceptor) + if err != nil { + if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { + // Print error if not due to help request. + err = fmt.Errorf("failed to load config: %w", err) + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + // Help was requested, exit normally. + os.Exit(0) + } + loadedConfig.Gossip.NoSync = true + implCfg := loadedConfig.ImplementationConfig(interceptor) + implCfg.GraphProvider = &graphProvider{lndProviders: lndProviders} + // Call the "real" main in a nested manner so the defers will properly + // be executed in the case of a graceful shutdown. + if err = lnd.Main( + loadedConfig, lnd.ListenerCfg{}, implCfg, interceptor, nil, + ); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +type graphProvider struct { + lndProviders lnd.Providers +} + +func (g *graphProvider) Graph(_ context.Context, + dbs *lnd.DatabaseInstances) (graphsources.GraphSource, error) { + + graphSource, err := g.lndProviders.GraphSource() + if err != nil { + return nil, err + } + + getLocalPub := func() (route.Vertex, error) { + node, err := dbs.GraphDB.SourceNode() + if err != nil { + return route.Vertex{}, err + } + + return route.NewVertexFromBytes(node.PubKeyBytes[:]) + } + + return graphsources.NewGraphBackend( + graphsources.NewDBGSource(dbs.GraphDB), graphSource, + getLocalPub, + ), nil +} + +func graphConfig() *lnd.Config { + cfg := lnd.DefaultConfig() + cfg.Bitcoin.RegTest = true + cfg.LndDir = "/Users/elle/.lnd-dev-graph" + cfg.BitcoindMode.RPCHost = "localhost:18443" + cfg.Bitcoin.Node = "bitcoind" + cfg.RawRPCListeners = []string{"localhost:10020"} + cfg.BitcoindMode.RPCUser = "lightning" + cfg.BitcoindMode.RPCPass = "lightning" + cfg.BitcoindMode.ZMQPubRawBlock = "tcp://localhost:28332" + cfg.BitcoindMode.ZMQPubRawTx = "tcp://localhost:28333" + cfg.TrickleDelay = 50 + cfg.NoSeedBackup = true + cfg.RawRESTListeners = []string{"localhost:11020"} + cfg.RawListeners = []string{"localhost:9736"} + cfg.DebugLevel = "debug" + cfg.LogConfig.Console.Disable = true + + return &cfg +} diff --git a/config.go b/config.go index c4f55ddf1a..d5511e6e69 100644 --- a/config.go +++ b/config.go @@ -861,6 +861,87 @@ func LoadConfig(interceptor signal.Interceptor) (*Config, error) { return cleanCfg, nil } +func LoadConfigNoFlags(preCfg Config, interceptor signal.Interceptor) (*Config, error) { + // Show the version and exit if the version flag was specified. + appName := filepath.Base(os.Args[0]) + appName = strings.TrimSuffix(appName, filepath.Ext(appName)) + usageMessage := fmt.Sprintf("Use %s -h to show usage", appName) + if preCfg.ShowVersion { + fmt.Println(appName, "version", build.Version(), + "commit="+build.Commit) + os.Exit(0) + } + // If the config file path has not been modified by the user, then we'll + // use the default config file path. However, if the user has modified + // their lnddir, then we should assume they intend to use the config + // file within it. + configFileDir := CleanAndExpandPath(preCfg.LndDir) + configFilePath := CleanAndExpandPath(preCfg.ConfigFile) + switch { + // User specified --lnddir but no --configfile. Update the config file + // path to the lnd config directory, but don't require it to exist. + case configFileDir != DefaultLndDir && + configFilePath == DefaultConfigFile: + configFilePath = filepath.Join( + configFileDir, lncfg.DefaultConfigFilename, + ) + // User did specify an explicit --configfile, so we check that it does + // exist under that path to avoid surprises. + case configFilePath != DefaultConfigFile: + if !lnrpc.FileExists(configFilePath) { + return nil, fmt.Errorf("specified config file does "+ + "not exist in %s", configFilePath) + } + } + // Next, load any additional configuration options from the file. + var configFileError error + cfg := preCfg + fileParser := flags.NewParser(&cfg, flags.Default) + err := flags.NewIniParser(fileParser).ParseFile(configFilePath) + if 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 lnutils.ErrorAs[*flags.IniError](err) || + lnutils.ErrorAs[*flags.Error](err) { + return nil, err + } + configFileError = err + } + // Make sure everything we just loaded makes sense. + cleanCfg, err := ValidateConfig( + cfg, interceptor, fileParser, nil, + ) + var usageErr *lncfg.UsageError + if errors.As(err, &usageErr) { + // The logging system might not yet be initialized, so we also + // write to stderr to make sure the error appears somewhere. + _, _ = fmt.Fprintln(os.Stderr, usageMessage) + ltndLog.Warnf("Incorrect usage: %v", usageMessage) + // The log subsystem might not yet be initialized. But we still + // try to log the error there since some packaging solutions + // might only look at the log and not stdout/stderr. + ltndLog.Warnf("Error validating config: %v", err) + return nil, err + } + if err != nil { + // The log subsystem might not yet be initialized. But we still + // try to log the error there since some packaging solutions + // might only look at the log and not stdout/stderr. + ltndLog.Warnf("Error validating config: %v", err) + return nil, err + } + // Warn about missing config file only after all other configuration is + // done. This prevents the warning on help messages and invalid options. + // Note this should go directly before the return. + if configFileError != nil { + ltndLog.Warnf("%v", configFileError) + } + // Finally, log warnings for deprecated config options if they are set. + logWarningsForDeprecation(*cleanCfg) + return cleanCfg, nil +} + // ValidateConfig check the given configuration to be sane. This makes sure no // illegal values or combination of values are set. All file system paths are // normalized. The cleaned up config is returned on success. @@ -941,11 +1022,18 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, fileOptionNested := fileParser.FindOptionByLongName( "lnd." + long, ) - flagOption := flagParser.FindOptionByLongName(long) - flagOptionNested := flagParser.FindOptionByLongName( - "lnd." + long, + var ( + flagOption *flags.Option + flagOptionNested *flags.Option ) + if flagParser != nil { + flagOption = flagParser.FindOptionByLongName(long) + flagOptionNested = flagParser.FindOptionByLongName( + "lnd." + long, + ) + } + return (fileOption != nil && fileOption.IsSet()) || (fileOptionNested != nil && fileOptionNested.IsSet()) || (flagOption != nil && flagOption.IsSet()) || diff --git a/graph/sources/chan_graph.go b/graph/sources/chan_graph.go index 99ee0f0eb9..6cf5ab871e 100644 --- a/graph/sources/chan_graph.go +++ b/graph/sources/chan_graph.go @@ -447,3 +447,12 @@ func extractKVDBRTx(tx session.RTx) (kvdb.RTx, error) { return kvdbTx, nil } + +func (s *DBSource) SelfNode() (route.Vertex, error) { + node, err := s.db.SourceNode() + if err != nil { + return route.Vertex{}, err + } + + return route.NewVertexFromBytes(node.PubKeyBytes[:]) +}