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.
This commit is contained in:
Wilmer Paulino 2019-02-11 18:35:45 -08:00 committed by Olaoluwa Osuntokun
parent d0757221b4
commit 1fe6599fd8
2 changed files with 93 additions and 78 deletions

View File

@ -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 {

79
lnd.go
View File

@ -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)