From 1fe6599fd8694d38cbfd9a73c5e5d31cfea880b4 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 11 Feb 2019 18:35:45 -0800 Subject: [PATCH] lnd+chainregistry: initialize neutrino light client before wallet setup In this commit, we slightly refactor the startup of lnd when running with a Neutrino light client backend. We'll now begin syncing our backend as soon as lnd starts and passes all configuration checks. Since this is all done before lnd's wallet setup, the light client will be syncing in the background while the user notes/inputs their wallet seed. This is done in order to provide a better UX from the point of the user, such that most of the chain will already be synced by the time they get to deposit funds into the wallet. --- chainregistry.go | 92 ++++++++---------------------------------------- lnd.go | 79 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 78 deletions(-) diff --git a/chainregistry.go b/chainregistry.go index 7f420ccb..7a29111f 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "net" "os" - "path/filepath" "strconv" "strings" "sync" @@ -17,7 +16,6 @@ import ( "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" - "github.com/btcsuite/btcwallet/walletdb" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" @@ -124,13 +122,15 @@ type chainControl struct { } // newChainControlFromConfig attempts to create a chainControl instance -// according to the parameters in the passed lnd configuration. Currently two +// according to the parameters in the passed lnd configuration. Currently three // branches of chainControl instances exist: one backed by a running btcd -// full-node, and the other backed by a running neutrino light client instance. +// full-node, another backed by a running bitcoind full-node, and the other +// backed by a running neutrino light client instance. When running with a +// neutrino light client instance, `neutrinoCS` must be non-nil. func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, privateWalletPw, publicWalletPw []byte, birthday time.Time, - recoveryWindow uint32, - wallet *wallet.Wallet) (*chainControl, func(), error) { + recoveryWindow uint32, wallet *wallet.Wallet, + neutrinoCS *neutrino.ChainService) (*chainControl, func(), error) { // Set the RPC config from the "home" chain. Multi-chain isn't yet // active, so we'll restrict usage to a particular chain for now. @@ -198,83 +198,21 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, // of the selected chain. switch homeChainConfig.Node { case "neutrino": - // First we'll open the database file for neutrino, creating - // the database if needed. We append the normalized network name - // here to match the behavior of btcwallet. - neutrinoDbPath := filepath.Join(homeChainConfig.ChainDir, - normalizeNetwork(activeNetParams.Name)) - - // Ensure that the neutrino db path exists. - if err := os.MkdirAll(neutrinoDbPath, 0700); err != nil { - return nil, nil, err - } - - dbName := filepath.Join(neutrinoDbPath, "neutrino.db") - nodeDatabase, err := walletdb.Create("bdb", dbName) - if err != nil { - return nil, nil, err - } - - // With the database open, we can now create an instance of the - // neutrino light client. We pass in relevant configuration - // parameters required. - config := neutrino.Config{ - DataDir: neutrinoDbPath, - Database: nodeDatabase, - ChainParams: *activeNetParams.Params, - AddPeers: cfg.NeutrinoMode.AddPeers, - ConnectPeers: cfg.NeutrinoMode.ConnectPeers, - Dialer: func(addr net.Addr) (net.Conn, error) { - return cfg.net.Dial(addr.Network(), addr.String()) - }, - NameResolver: func(host string) ([]net.IP, error) { - addrs, err := cfg.net.LookupHost(host) - if err != nil { - return nil, err - } - - ips := make([]net.IP, 0, len(addrs)) - for _, strIP := range addrs { - ip := net.ParseIP(strIP) - if ip == nil { - continue - } - - ips = append(ips, ip) - } - - return ips, nil - }, - } - neutrino.MaxPeers = 8 - neutrino.BanDuration = 5 * time.Second - svc, err := neutrino.NewChainService(config) - if err != nil { - nodeDatabase.Close() - return nil, nil, fmt.Errorf("unable to create neutrino: %v", err) - } - svc.Start() - cleanUp = func() { - svc.Stop() - nodeDatabase.Close() - } - - // Next we'll create the instances of the ChainNotifier and - // FilteredChainView interface which is backed by the neutrino - // light client. - cc.chainNotifier = neutrinonotify.New(svc, hintCache, hintCache) - cc.chainView, err = chainview.NewCfFilteredChainView(svc) + // We'll create ChainNotifier and FilteredChainView instances, + // along with the wallet's ChainSource, which are all backed by + // the neutrino light client. + cc.chainNotifier = neutrinonotify.New( + neutrinoCS, hintCache, hintCache, + ) + cc.chainView, err = chainview.NewCfFilteredChainView(neutrinoCS) if err != nil { cleanUp() return nil, nil, err } - - // Finally, we'll set the chain source for btcwallet, and - // create our clean up function which simply closes the - // database. walletConfig.ChainSource = chain.NewNeutrinoClient( - activeNetParams.Params, svc, + activeNetParams.Params, neutrinoCS, ) + case "bitcoind", "litecoind": var bitcoindMode *bitcoindConfig switch { diff --git a/lnd.go b/lnd.go index 96e5b9d6..329acf40 100644 --- a/lnd.go +++ b/lnd.go @@ -35,8 +35,10 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" flags "github.com/jessevdk/go-flags" + "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/build" @@ -196,6 +198,81 @@ func lndMain() error { } proxyOpts := []grpc.DialOption{grpc.WithTransportCredentials(cCreds)} + // Before starting the wallet, we'll create and start our Neutrino light + // client instance, if enabled, in order to allow it to sync while the + // rest of the daemon continues startup. + mainChain := cfg.Bitcoin + if registeredChains.PrimaryChain() == litecoinChain { + mainChain = cfg.Litecoin + } + var neutrinoCS *neutrino.ChainService + if mainChain.Node == "neutrino" { + // First we'll open the database file for neutrino, creating + // the database if needed. We append the normalized network name + // here to match the behavior of btcwallet. + dbPath := filepath.Join( + mainChain.ChainDir, + normalizeNetwork(activeNetParams.Name), + ) + + // Ensure that the neutrino db path exists. + if err := os.MkdirAll(dbPath, 0700); err != nil { + return err + } + + dbName := filepath.Join(dbPath, "neutrino.db") + db, err := walletdb.Create("bdb", dbName) + if err != nil { + return fmt.Errorf("unable to create neutrino "+ + "database: %v", err) + } + defer db.Close() + + // With the database open, we can now create an instance of the + // neutrino light client. We pass in relevant configuration + // parameters required. + config := neutrino.Config{ + DataDir: dbPath, + Database: db, + ChainParams: *activeNetParams.Params, + AddPeers: cfg.NeutrinoMode.AddPeers, + ConnectPeers: cfg.NeutrinoMode.ConnectPeers, + Dialer: func(addr net.Addr) (net.Conn, error) { + return cfg.net.Dial(addr.Network(), addr.String()) + }, + NameResolver: func(host string) ([]net.IP, error) { + addrs, err := cfg.net.LookupHost(host) + if err != nil { + return nil, err + } + + ips := make([]net.IP, 0, len(addrs)) + for _, strIP := range addrs { + ip := net.ParseIP(strIP) + if ip == nil { + continue + } + + ips = append(ips, ip) + } + + return ips, nil + }, + } + + neutrino.MaxPeers = 8 + neutrino.BanDuration = 5 * time.Second + + neutrinoCS, err = neutrino.NewChainService(config) + if err != nil { + return fmt.Errorf("unable to create neutrino light "+ + "client: %v", err) + } + + neutrinoCS.Start() + defer neutrinoCS.Stop() + } + var ( privateWalletPw = lnwallet.DefaultPrivatePassphrase publicWalletPw = lnwallet.DefaultPublicPassphrase @@ -269,7 +346,7 @@ func lndMain() error { // Lightning Network Daemon. activeChainControl, chainCleanUp, err := newChainControlFromConfig( cfg, chanDB, privateWalletPw, publicWalletPw, birthday, - recoveryWindow, unlockedWallet, + recoveryWindow, unlockedWallet, neutrinoCS, ) if err != nil { fmt.Printf("unable to create chain control: %v\n", err)