mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 14:40:30 +01:00
config_builder+lnd: move wallet related code
We move some of the wallet related functions into the new file that houses the new customizable implementations for some of our interfaces. Since the next customizable interface will be the wallet, we move those wallet and chain backend related helper functions. NOTE: This is a pure code move.
This commit is contained in:
parent
f6d7e70f51
commit
047d8ea3bc
2 changed files with 323 additions and 315 deletions
|
@ -3,17 +3,30 @@ package lnd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btclog"
|
"github.com/btcsuite/btclog"
|
||||||
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
|
"github.com/lightninglabs/neutrino"
|
||||||
|
"github.com/lightninglabs/neutrino/headerfs"
|
||||||
|
"github.com/lightningnetwork/lnd/blockcache"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
|
"github.com/lightningnetwork/lnd/walletunlocker"
|
||||||
"github.com/lightningnetwork/lnd/watchtower"
|
"github.com/lightningnetwork/lnd/watchtower"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtclient"
|
"github.com/lightningnetwork/lnd/watchtower/wtclient"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
|
@ -343,3 +356,313 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
|
||||||
|
|
||||||
return dbs, cleanUp, nil
|
return dbs, cleanUp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForWalletPassword blocks until a password is provided by the user to
|
||||||
|
// this RPC server.
|
||||||
|
func waitForWalletPassword(cfg *Config,
|
||||||
|
pwService *walletunlocker.UnlockerService,
|
||||||
|
loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
|
||||||
|
*walletunlocker.WalletUnlockParams, error) {
|
||||||
|
|
||||||
|
// Wait for user to provide the password.
|
||||||
|
ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
|
||||||
|
"create` to create a wallet, `lncli unlock` to unlock an " +
|
||||||
|
"existing wallet, or `lncli changepassword` to change the " +
|
||||||
|
"password of an existing wallet and unlock it.")
|
||||||
|
|
||||||
|
// We currently don't distinguish between getting a password to be used
|
||||||
|
// for creation or unlocking, as a new wallet db will be created if
|
||||||
|
// none exists when creating the chain control.
|
||||||
|
select {
|
||||||
|
|
||||||
|
// The wallet is being created for the first time, we'll check to see
|
||||||
|
// if the user provided any entropy for seed creation. If so, then
|
||||||
|
// we'll create the wallet early to load the seed.
|
||||||
|
case initMsg := <-pwService.InitMsgs:
|
||||||
|
password := initMsg.Passphrase
|
||||||
|
cipherSeed := initMsg.WalletSeed
|
||||||
|
extendedKey := initMsg.WalletExtendedKey
|
||||||
|
recoveryWindow := initMsg.RecoveryWindow
|
||||||
|
|
||||||
|
// Before we proceed, we'll check the internal version of the
|
||||||
|
// seed. If it's greater than the current key derivation
|
||||||
|
// version, then we'll return an error as we don't understand
|
||||||
|
// this.
|
||||||
|
const latestVersion = keychain.KeyDerivationVersion
|
||||||
|
if cipherSeed != nil &&
|
||||||
|
cipherSeed.InternalVersion != latestVersion {
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid internal "+
|
||||||
|
"seed version %v, current version is %v",
|
||||||
|
cipherSeed.InternalVersion,
|
||||||
|
keychain.KeyDerivationVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
loader, err := btcwallet.NewWalletLoader(
|
||||||
|
cfg.ActiveNetParams.Params, recoveryWindow,
|
||||||
|
loaderOpts...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the seed, we can now use the wallet loader to create
|
||||||
|
// the wallet, then pass it back to avoid unlocking it again.
|
||||||
|
var (
|
||||||
|
birthday time.Time
|
||||||
|
newWallet *wallet.Wallet
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
// A normal cipher seed was given, use the birthday encoded in
|
||||||
|
// it and create the wallet from that.
|
||||||
|
case cipherSeed != nil:
|
||||||
|
birthday = cipherSeed.BirthdayTime()
|
||||||
|
newWallet, err = loader.CreateNewWallet(
|
||||||
|
password, password, cipherSeed.Entropy[:],
|
||||||
|
birthday,
|
||||||
|
)
|
||||||
|
|
||||||
|
// No seed was given, we're importing a wallet from its extended
|
||||||
|
// private key.
|
||||||
|
case extendedKey != nil:
|
||||||
|
birthday = initMsg.ExtendedKeyBirthday
|
||||||
|
newWallet, err = loader.CreateNewWalletExtendedKey(
|
||||||
|
password, password, extendedKey, birthday,
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// The unlocker service made sure either the cipher seed
|
||||||
|
// or the extended key is set so, we shouldn't get here.
|
||||||
|
// The default case is just here for readability and
|
||||||
|
// completeness.
|
||||||
|
err = fmt.Errorf("cannot create wallet, neither seed " +
|
||||||
|
"nor extended key was given")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Don't leave the file open in case the new wallet
|
||||||
|
// could not be created for whatever reason.
|
||||||
|
if err := loader.UnloadWallet(); err != nil {
|
||||||
|
ltndLog.Errorf("Could not unload new "+
|
||||||
|
"wallet: %v", err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For new wallets, the ResetWalletTransactions flag is a no-op.
|
||||||
|
if cfg.ResetWalletTransactions {
|
||||||
|
ltndLog.Warnf("Ignoring reset-wallet-transactions " +
|
||||||
|
"flag for new wallet as it has no effect")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &walletunlocker.WalletUnlockParams{
|
||||||
|
Password: password,
|
||||||
|
Birthday: birthday,
|
||||||
|
RecoveryWindow: recoveryWindow,
|
||||||
|
Wallet: newWallet,
|
||||||
|
ChansToRestore: initMsg.ChanBackups,
|
||||||
|
UnloadWallet: loader.UnloadWallet,
|
||||||
|
StatelessInit: initMsg.StatelessInit,
|
||||||
|
MacResponseChan: pwService.MacResponseChan,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
// The wallet has already been created in the past, and is simply being
|
||||||
|
// unlocked. So we'll just return these passphrases.
|
||||||
|
case unlockMsg := <-pwService.UnlockMsgs:
|
||||||
|
// Resetting the transactions is something the user likely only
|
||||||
|
// wants to do once so we add a prominent warning to the log to
|
||||||
|
// remind the user to turn off the setting again after
|
||||||
|
// successful completion.
|
||||||
|
if cfg.ResetWalletTransactions {
|
||||||
|
ltndLog.Warnf("Dropped all transaction history from " +
|
||||||
|
"on-chain wallet. Remember to disable " +
|
||||||
|
"reset-wallet-transactions flag for next " +
|
||||||
|
"start of lnd")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &walletunlocker.WalletUnlockParams{
|
||||||
|
Password: unlockMsg.Passphrase,
|
||||||
|
RecoveryWindow: unlockMsg.RecoveryWindow,
|
||||||
|
Wallet: unlockMsg.Wallet,
|
||||||
|
ChansToRestore: unlockMsg.ChanBackups,
|
||||||
|
UnloadWallet: unlockMsg.UnloadWallet,
|
||||||
|
StatelessInit: unlockMsg.StatelessInit,
|
||||||
|
MacResponseChan: pwService.MacResponseChan,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
// If we got a shutdown signal we just return with an error immediately
|
||||||
|
case <-shutdownChan:
|
||||||
|
return nil, fmt.Errorf("shutting down")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initNeutrinoBackend inits a new instance of the neutrino light client
|
||||||
|
// backend given a target chain directory to store the chain state.
|
||||||
|
func initNeutrinoBackend(cfg *Config, chainDir string,
|
||||||
|
blockCache *blockcache.BlockCache) (*neutrino.ChainService,
|
||||||
|
func(), error) {
|
||||||
|
|
||||||
|
// Both channel validation flags are false by default but their meaning
|
||||||
|
// is the inverse of each other. Therefore both cannot be true. For
|
||||||
|
// every other case, the neutrino.validatechannels overwrites the
|
||||||
|
// routing.assumechanvalid value.
|
||||||
|
if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
|
||||||
|
return nil, nil, fmt.Errorf("can't set both " +
|
||||||
|
"neutrino.validatechannels and routing." +
|
||||||
|
"assumechanvalid to true at the same time")
|
||||||
|
}
|
||||||
|
cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure that the neutrino db path exists.
|
||||||
|
if err := os.MkdirAll(dbPath, 0700); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbName := filepath.Join(dbPath, "neutrino.db")
|
||||||
|
db, err := walletdb.Create(
|
||||||
|
"bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to create neutrino "+
|
||||||
|
"database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerStateAssertion, err := parseHeaderStateAssertion(
|
||||||
|
cfg.NeutrinoMode.AssertFilterHeader,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
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: dbPath,
|
||||||
|
Database: db,
|
||||||
|
ChainParams: *cfg.ActiveNetParams.Params,
|
||||||
|
AddPeers: cfg.NeutrinoMode.AddPeers,
|
||||||
|
ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
|
||||||
|
Dialer: func(addr net.Addr) (net.Conn, error) {
|
||||||
|
dialAddr := addr
|
||||||
|
if tor.IsOnionFakeIP(addr) {
|
||||||
|
// Because the Neutrino address manager only
|
||||||
|
// knows IP addresses, we need to turn any fake
|
||||||
|
// tcp6 address that actually encodes an Onion
|
||||||
|
// v2 address back into the hostname
|
||||||
|
// representation before we can pass it to the
|
||||||
|
// dialer.
|
||||||
|
var err error
|
||||||
|
dialAddr, err = tor.FakeIPToOnionHost(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.net.Dial(
|
||||||
|
dialAddr.Network(), dialAddr.String(),
|
||||||
|
cfg.ConnectionTimeout,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
NameResolver: func(host string) ([]net.IP, error) {
|
||||||
|
if tor.IsOnionHost(host) {
|
||||||
|
// Neutrino internally uses btcd's address
|
||||||
|
// manager which only operates on an IP level
|
||||||
|
// and does not understand onion hosts. We need
|
||||||
|
// to turn an onion host into a fake
|
||||||
|
// representation of an IP address to make it
|
||||||
|
// possible to connect to a block filter backend
|
||||||
|
// that serves on an Onion v2 hidden service.
|
||||||
|
fakeIP, err := tor.OnionHostToFakeIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []net.IP{fakeIP}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
AssertFilterHeader: headerStateAssertion,
|
||||||
|
BlockCache: blockCache.Cache,
|
||||||
|
BroadcastTimeout: cfg.NeutrinoMode.BroadcastTimeout,
|
||||||
|
PersistToDisk: cfg.NeutrinoMode.PersistFilters,
|
||||||
|
}
|
||||||
|
|
||||||
|
neutrino.MaxPeers = 8
|
||||||
|
neutrino.BanDuration = time.Hour * 48
|
||||||
|
neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
|
||||||
|
neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
|
||||||
|
|
||||||
|
neutrinoCS, err := neutrino.NewChainService(config)
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, nil, fmt.Errorf("unable to create neutrino light "+
|
||||||
|
"client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := neutrinoCS.Start(); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanUp := func() {
|
||||||
|
if err := neutrinoCS.Stop(); err != nil {
|
||||||
|
ltndLog.Infof("Unable to stop neutrino light client: %v", err)
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return neutrinoCS, cleanUp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHeaderStateAssertion parses the user-specified neutrino header state
|
||||||
|
// into a headerfs.FilterHeader.
|
||||||
|
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
|
||||||
|
if len(state) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(state, ":")
|
||||||
|
if len(split) != 2 {
|
||||||
|
return nil, fmt.Errorf("header state assertion %v in "+
|
||||||
|
"unexpected format, expected format height:hash", state)
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.ParseUint(split[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid filter header height: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := chainhash.NewHashFromStr(split[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid filter header hash: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &headerfs.FilterHeader{
|
||||||
|
Height: uint32(height),
|
||||||
|
FilterHash: *hash,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
315
lnd.go
315
lnd.go
|
@ -15,20 +15,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof" // Blank import to set up profiling HTTP handlers.
|
_ "net/http/pprof" // Blank import to set up profiling HTTP handlers.
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcwallet/wallet"
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
"github.com/btcsuite/btcwallet/walletdb"
|
|
||||||
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
"github.com/lightninglabs/neutrino"
|
"github.com/lightninglabs/neutrino"
|
||||||
"github.com/lightninglabs/neutrino/headerfs"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
@ -1417,313 +1412,3 @@ func startRestProxy(cfg *Config, rpcServer *rpcServer, restDialOpts []grpc.DialO
|
||||||
|
|
||||||
return shutdown, nil
|
return shutdown, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForWalletPassword blocks until a password is provided by the user to
|
|
||||||
// this RPC server.
|
|
||||||
func waitForWalletPassword(cfg *Config,
|
|
||||||
pwService *walletunlocker.UnlockerService,
|
|
||||||
loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
|
|
||||||
*walletunlocker.WalletUnlockParams, error) {
|
|
||||||
|
|
||||||
// Wait for user to provide the password.
|
|
||||||
ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
|
|
||||||
"create` to create a wallet, `lncli unlock` to unlock an " +
|
|
||||||
"existing wallet, or `lncli changepassword` to change the " +
|
|
||||||
"password of an existing wallet and unlock it.")
|
|
||||||
|
|
||||||
// We currently don't distinguish between getting a password to be used
|
|
||||||
// for creation or unlocking, as a new wallet db will be created if
|
|
||||||
// none exists when creating the chain control.
|
|
||||||
select {
|
|
||||||
|
|
||||||
// The wallet is being created for the first time, we'll check to see
|
|
||||||
// if the user provided any entropy for seed creation. If so, then
|
|
||||||
// we'll create the wallet early to load the seed.
|
|
||||||
case initMsg := <-pwService.InitMsgs:
|
|
||||||
password := initMsg.Passphrase
|
|
||||||
cipherSeed := initMsg.WalletSeed
|
|
||||||
extendedKey := initMsg.WalletExtendedKey
|
|
||||||
recoveryWindow := initMsg.RecoveryWindow
|
|
||||||
|
|
||||||
// Before we proceed, we'll check the internal version of the
|
|
||||||
// seed. If it's greater than the current key derivation
|
|
||||||
// version, then we'll return an error as we don't understand
|
|
||||||
// this.
|
|
||||||
const latestVersion = keychain.KeyDerivationVersion
|
|
||||||
if cipherSeed != nil &&
|
|
||||||
cipherSeed.InternalVersion != latestVersion {
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("invalid internal "+
|
|
||||||
"seed version %v, current version is %v",
|
|
||||||
cipherSeed.InternalVersion,
|
|
||||||
keychain.KeyDerivationVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
loader, err := btcwallet.NewWalletLoader(
|
|
||||||
cfg.ActiveNetParams.Params, recoveryWindow,
|
|
||||||
loaderOpts...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the seed, we can now use the wallet loader to create
|
|
||||||
// the wallet, then pass it back to avoid unlocking it again.
|
|
||||||
var (
|
|
||||||
birthday time.Time
|
|
||||||
newWallet *wallet.Wallet
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
// A normal cipher seed was given, use the birthday encoded in
|
|
||||||
// it and create the wallet from that.
|
|
||||||
case cipherSeed != nil:
|
|
||||||
birthday = cipherSeed.BirthdayTime()
|
|
||||||
newWallet, err = loader.CreateNewWallet(
|
|
||||||
password, password, cipherSeed.Entropy[:],
|
|
||||||
birthday,
|
|
||||||
)
|
|
||||||
|
|
||||||
// No seed was given, we're importing a wallet from its extended
|
|
||||||
// private key.
|
|
||||||
case extendedKey != nil:
|
|
||||||
birthday = initMsg.ExtendedKeyBirthday
|
|
||||||
newWallet, err = loader.CreateNewWalletExtendedKey(
|
|
||||||
password, password, extendedKey, birthday,
|
|
||||||
)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// The unlocker service made sure either the cipher seed
|
|
||||||
// or the extended key is set so, we shouldn't get here.
|
|
||||||
// The default case is just here for readability and
|
|
||||||
// completeness.
|
|
||||||
err = fmt.Errorf("cannot create wallet, neither seed " +
|
|
||||||
"nor extended key was given")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// Don't leave the file open in case the new wallet
|
|
||||||
// could not be created for whatever reason.
|
|
||||||
if err := loader.UnloadWallet(); err != nil {
|
|
||||||
ltndLog.Errorf("Could not unload new "+
|
|
||||||
"wallet: %v", err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// For new wallets, the ResetWalletTransactions flag is a no-op.
|
|
||||||
if cfg.ResetWalletTransactions {
|
|
||||||
ltndLog.Warnf("Ignoring reset-wallet-transactions " +
|
|
||||||
"flag for new wallet as it has no effect")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &walletunlocker.WalletUnlockParams{
|
|
||||||
Password: password,
|
|
||||||
Birthday: birthday,
|
|
||||||
RecoveryWindow: recoveryWindow,
|
|
||||||
Wallet: newWallet,
|
|
||||||
ChansToRestore: initMsg.ChanBackups,
|
|
||||||
UnloadWallet: loader.UnloadWallet,
|
|
||||||
StatelessInit: initMsg.StatelessInit,
|
|
||||||
MacResponseChan: pwService.MacResponseChan,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// The wallet has already been created in the past, and is simply being
|
|
||||||
// unlocked. So we'll just return these passphrases.
|
|
||||||
case unlockMsg := <-pwService.UnlockMsgs:
|
|
||||||
// Resetting the transactions is something the user likely only
|
|
||||||
// wants to do once so we add a prominent warning to the log to
|
|
||||||
// remind the user to turn off the setting again after
|
|
||||||
// successful completion.
|
|
||||||
if cfg.ResetWalletTransactions {
|
|
||||||
ltndLog.Warnf("Dropped all transaction history from " +
|
|
||||||
"on-chain wallet. Remember to disable " +
|
|
||||||
"reset-wallet-transactions flag for next " +
|
|
||||||
"start of lnd")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &walletunlocker.WalletUnlockParams{
|
|
||||||
Password: unlockMsg.Passphrase,
|
|
||||||
RecoveryWindow: unlockMsg.RecoveryWindow,
|
|
||||||
Wallet: unlockMsg.Wallet,
|
|
||||||
ChansToRestore: unlockMsg.ChanBackups,
|
|
||||||
UnloadWallet: unlockMsg.UnloadWallet,
|
|
||||||
StatelessInit: unlockMsg.StatelessInit,
|
|
||||||
MacResponseChan: pwService.MacResponseChan,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// If we got a shutdown signal we just return with an error immediately
|
|
||||||
case <-shutdownChan:
|
|
||||||
return nil, fmt.Errorf("shutting down")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initNeutrinoBackend inits a new instance of the neutrino light client
|
|
||||||
// backend given a target chain directory to store the chain state.
|
|
||||||
func initNeutrinoBackend(cfg *Config, chainDir string,
|
|
||||||
blockCache *blockcache.BlockCache) (*neutrino.ChainService,
|
|
||||||
func(), error) {
|
|
||||||
|
|
||||||
// Both channel validation flags are false by default but their meaning
|
|
||||||
// is the inverse of each other. Therefore both cannot be true. For
|
|
||||||
// every other case, the neutrino.validatechannels overwrites the
|
|
||||||
// routing.assumechanvalid value.
|
|
||||||
if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
|
|
||||||
return nil, nil, fmt.Errorf("can't set both " +
|
|
||||||
"neutrino.validatechannels and routing." +
|
|
||||||
"assumechanvalid to true at the same time")
|
|
||||||
}
|
|
||||||
cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that the neutrino db path exists.
|
|
||||||
if err := os.MkdirAll(dbPath, 0700); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dbName := filepath.Join(dbPath, "neutrino.db")
|
|
||||||
db, err := walletdb.Create(
|
|
||||||
"bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create neutrino "+
|
|
||||||
"database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headerStateAssertion, err := parseHeaderStateAssertion(
|
|
||||||
cfg.NeutrinoMode.AssertFilterHeader,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
db.Close()
|
|
||||||
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: dbPath,
|
|
||||||
Database: db,
|
|
||||||
ChainParams: *cfg.ActiveNetParams.Params,
|
|
||||||
AddPeers: cfg.NeutrinoMode.AddPeers,
|
|
||||||
ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
|
|
||||||
Dialer: func(addr net.Addr) (net.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if tor.IsOnionFakeIP(addr) {
|
|
||||||
// Because the Neutrino address manager only
|
|
||||||
// knows IP addresses, we need to turn any fake
|
|
||||||
// tcp6 address that actually encodes an Onion
|
|
||||||
// v2 address back into the hostname
|
|
||||||
// representation before we can pass it to the
|
|
||||||
// dialer.
|
|
||||||
var err error
|
|
||||||
dialAddr, err = tor.FakeIPToOnionHost(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg.net.Dial(
|
|
||||||
dialAddr.Network(), dialAddr.String(),
|
|
||||||
cfg.ConnectionTimeout,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
NameResolver: func(host string) ([]net.IP, error) {
|
|
||||||
if tor.IsOnionHost(host) {
|
|
||||||
// Neutrino internally uses btcd's address
|
|
||||||
// manager which only operates on an IP level
|
|
||||||
// and does not understand onion hosts. We need
|
|
||||||
// to turn an onion host into a fake
|
|
||||||
// representation of an IP address to make it
|
|
||||||
// possible to connect to a block filter backend
|
|
||||||
// that serves on an Onion v2 hidden service.
|
|
||||||
fakeIP, err := tor.OnionHostToFakeIP(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []net.IP{fakeIP}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
AssertFilterHeader: headerStateAssertion,
|
|
||||||
BlockCache: blockCache.Cache,
|
|
||||||
BroadcastTimeout: cfg.NeutrinoMode.BroadcastTimeout,
|
|
||||||
PersistToDisk: cfg.NeutrinoMode.PersistFilters,
|
|
||||||
}
|
|
||||||
|
|
||||||
neutrino.MaxPeers = 8
|
|
||||||
neutrino.BanDuration = time.Hour * 48
|
|
||||||
neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
|
|
||||||
neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
|
|
||||||
|
|
||||||
neutrinoCS, err := neutrino.NewChainService(config)
|
|
||||||
if err != nil {
|
|
||||||
db.Close()
|
|
||||||
return nil, nil, fmt.Errorf("unable to create neutrino light "+
|
|
||||||
"client: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := neutrinoCS.Start(); err != nil {
|
|
||||||
db.Close()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUp := func() {
|
|
||||||
if err := neutrinoCS.Stop(); err != nil {
|
|
||||||
ltndLog.Infof("Unable to stop neutrino light client: %v", err)
|
|
||||||
}
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return neutrinoCS, cleanUp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseHeaderStateAssertion parses the user-specified neutrino header state
|
|
||||||
// into a headerfs.FilterHeader.
|
|
||||||
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
|
|
||||||
if len(state) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
split := strings.Split(state, ":")
|
|
||||||
if len(split) != 2 {
|
|
||||||
return nil, fmt.Errorf("header state assertion %v in "+
|
|
||||||
"unexpected format, expected format height:hash", state)
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.ParseUint(split[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid filter header height: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := chainhash.NewHashFromStr(split[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid filter header hash: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &headerfs.FilterHeader{
|
|
||||||
Height: uint32(height),
|
|
||||||
FilterHash: *hash,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue