Files
breez-lnd/config_builder.go
Oliver Gugger 047d8ea3bc 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.
2021-10-08 12:08:25 +02:00

669 lines
22 KiB
Go

package lnd
import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btclog"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
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/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"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/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
// GrpcRegistrar is an interface that must be satisfied by an external subserver
// that wants to be able to register its own gRPC server onto lnd's main
// grpc.Server instance.
type GrpcRegistrar interface {
// RegisterGrpcSubserver is called for each net.Listener on which lnd
// creates a grpc.Server instance. External subservers implementing this
// method can then register their own gRPC server structs to the main
// server instance.
RegisterGrpcSubserver(*grpc.Server) error
}
// RestRegistrar is an interface that must be satisfied by an external subserver
// that wants to be able to register its own REST mux onto lnd's main
// proxy.ServeMux instance.
type RestRegistrar interface {
// RegisterRestSubserver is called after lnd creates the main
// proxy.ServeMux instance. External subservers implementing this method
// can then register their own REST proxy stubs to the main server
// instance.
RegisterRestSubserver(context.Context, *proxy.ServeMux, string,
[]grpc.DialOption) error
}
// ExternalValidator is an interface that must be satisfied by an external
// macaroon validator.
type ExternalValidator interface {
macaroons.MacaroonValidator
// Permissions returns the permissions that the external validator is
// validating. It is a map between the full HTTP URI of each RPC and its
// required macaroon permissions. If multiple action/entity tuples are
// specified per URI, they are all required. See rpcserver.go for a list
// of valid action and entity values.
Permissions() map[string][]bakery.Op
}
// DatabaseBuilder is an interface that must be satisfied by the implementation
// that provides lnd's main database backend instances.
type DatabaseBuilder interface {
// BuildDatabase extracts the current databases that we'll use for
// normal operation in the daemon. A function closure that closes all
// opened databases is also returned.
BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error)
}
// ImplementationCfg is a struct that holds all configuration items for
// components that can be implemented outside lnd itself.
type ImplementationCfg struct {
// GrpcRegistrar is a type that can register additional gRPC subservers
// before the main gRPC server is started.
GrpcRegistrar
// RestRegistrar is a type that can register additional REST subservers
// before the main REST proxy is started.
RestRegistrar
// ExternalValidator is a type that can provide external macaroon
// validation.
ExternalValidator
// DatabaseBuilder is a type that can provide lnd's main database
// backend instances.
DatabaseBuilder
}
// DefaultWalletImpl is the default implementation of our normal, btcwallet
// backed configuration.
type DefaultWalletImpl struct {
}
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
// instance. External subservers implementing this method can then register
// their own REST proxy stubs to the main server instance.
//
// NOTE: This is part of the GrpcRegistrar interface.
func (d *DefaultWalletImpl) RegisterRestSubserver(context.Context,
*proxy.ServeMux, string, []grpc.DialOption) error {
return nil
}
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
// grpc.Server instance. External subservers implementing this method can then
// register their own gRPC server structs to the main server instance.
//
// NOTE: This is part of the GrpcRegistrar interface.
func (d *DefaultWalletImpl) RegisterGrpcSubserver(*grpc.Server) error {
return nil
}
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
// checks its signature, makes sure all specified permissions for the called
// method are contained within and finally ensures all caveat conditions are
// met. A non-nil error is returned if any of the checks fail.
//
// NOTE: This is part of the ExternalValidator interface.
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error {
// Because the default implementation does not return any permissions,
// we shouldn't be registered as an external validator at all and this
// should never be invoked.
return fmt.Errorf("default implementation does not support external " +
"macaroon validation")
}
// Permissions returns the permissions that the external validator is
// validating. It is a map between the full HTTP URI of each RPC and its
// required macaroon permissions. If multiple action/entity tuples are specified
// per URI, they are all required. See rpcserver.go for a list of valid action
// and entity values.
//
// NOTE: This is part of the ExternalValidator interface.
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
return nil
}
// DatabaseInstances is a struct that holds all instances to the actual
// databases that are used in lnd.
type DatabaseInstances struct {
// GraphDB is the database that stores the channel graph used for path
// finding.
//
// NOTE/TODO: This currently _needs_ to be the same instance as the
// ChanStateDB below until the separation of the two databases is fully
// complete!
GraphDB *channeldb.DB
// ChanStateDB is the database that stores all of our node's channel
// state.
//
// NOTE/TODO: This currently _needs_ to be the same instance as the
// GraphDB above until the separation of the two databases is fully
// complete!
ChanStateDB *channeldb.DB
// HeightHintDB is the database that stores height hints for spends.
HeightHintDB kvdb.Backend
// MacaroonDB is the database that stores macaroon root keys.
MacaroonDB kvdb.Backend
// DecayedLogDB is the database that stores p2p related encryption
// information.
DecayedLogDB kvdb.Backend
// TowerClientDB is the database that stores the watchtower client's
// configuration.
TowerClientDB wtclient.DB
// TowerServerDB is the database that stores the watchtower server's
// configuration.
TowerServerDB watchtower.DB
// WalletDB is the configuration for loading the wallet database using
// the btcwallet's loader.
WalletDB btcwallet.LoaderOption
}
// DefaultDatabaseBuilder is a type that builds the default database backends
// for lnd, using the given configuration to decide what actual implementation
// to use.
type DefaultDatabaseBuilder struct {
cfg *Config
logger btclog.Logger
}
// NewDefaultDatabaseBuilder returns a new instance of the default database
// builder.
func NewDefaultDatabaseBuilder(cfg *Config,
logger btclog.Logger) *DefaultDatabaseBuilder {
return &DefaultDatabaseBuilder{
cfg: cfg,
logger: logger,
}
}
// BuildDatabase extracts the current databases that we'll use for normal
// operation in the daemon. A function closure that closes all opened databases
// is also returned.
func (d *DefaultDatabaseBuilder) BuildDatabase(
ctx context.Context) (*DatabaseInstances, func(), error) {
d.logger.Infof("Opening the main database, this might take a few " +
"minutes...")
cfg := d.cfg
if cfg.DB.Backend == lncfg.BoltBackend {
d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
"auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
cfg.DB.Bolt.AutoCompact)
}
startOpenTime := time.Now()
databaseBackends, err := cfg.DB.GetBackends(
ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
cfg.Watchtower.TowerDir,
cfg.registeredChains.PrimaryChain().String(),
lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
), cfg.WtClient.Active, cfg.Watchtower.Active,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to obtain database "+
"backends: %v", err)
}
// With the full remote mode we made sure both the graph and channel
// state DB point to the same local or remote DB and the same namespace
// within that DB.
dbs := &DatabaseInstances{
HeightHintDB: databaseBackends.HeightHintDB,
MacaroonDB: databaseBackends.MacaroonDB,
DecayedLogDB: databaseBackends.DecayedLogDB,
WalletDB: databaseBackends.WalletDB,
}
cleanUp := func() {
// We can just close the returned close functions directly. Even
// if we decorate the channel DB with an additional struct, its
// close function still just points to the kvdb backend.
for name, closeFunc := range databaseBackends.CloseFuncs {
if err := closeFunc(); err != nil {
d.logger.Errorf("Error closing %s "+
"database: %v", name, err)
}
}
}
if databaseBackends.Remote {
d.logger.Infof("Using remote %v database! Creating "+
"graph and channel state DB instances", cfg.DB.Backend)
} else {
d.logger.Infof("Creating local graph and channel state DB " +
"instances")
}
dbOptions := []channeldb.OptionModifier{
channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize),
channeldb.OptionSetChannelCacheSize(cfg.Caches.ChannelCacheSize),
channeldb.OptionSetBatchCommitInterval(cfg.DB.BatchCommitInterval),
channeldb.OptionDryRunMigration(cfg.DryRunMigration),
}
// We want to pre-allocate the channel graph cache according to what we
// expect for mainnet to speed up memory allocation.
if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
dbOptions = append(
dbOptions, channeldb.OptionSetPreAllocCacheNumNodes(
channeldb.DefaultPreAllocCacheNumNodes,
),
)
}
// Otherwise, we'll open two instances, one for the state we only need
// locally, and the other for things we want to ensure are replicated.
dbs.GraphDB, err = channeldb.CreateWithBackend(
databaseBackends.GraphDB, dbOptions...,
)
switch {
// Give the DB a chance to dry run the migration. Since we know that
// both the channel state and graph DBs are still always behind the same
// backend, we know this would be applied to both of those DBs.
case err == channeldb.ErrDryRunMigrationOK:
d.logger.Infof("Graph DB dry run migration successful")
return nil, nil, err
case err != nil:
cleanUp()
err := fmt.Errorf("unable to open graph DB: %v", err)
d.logger.Error(err)
return nil, nil, err
}
// For now, we don't _actually_ split the graph and channel state DBs on
// the code level. Since they both are based upon the *channeldb.DB
// struct it will require more refactoring to fully separate them. With
// the full remote mode we at least know for now that they both point to
// the same DB backend (and also namespace within that) so we only need
// to apply any migration once.
//
// TODO(guggero): Once the full separation of anything graph related
// from the channeldb.DB is complete, the decorated instance of the
// channel state DB should be created here individually instead of just
// using the same struct (and DB backend) instance.
dbs.ChanStateDB = dbs.GraphDB
// Wrap the watchtower client DB and make sure we clean up.
if cfg.WtClient.Active {
dbs.TowerClientDB, err = wtdb.OpenClientDB(
databaseBackends.TowerClientDB,
)
if err != nil {
cleanUp()
err := fmt.Errorf("unable to open %s database: %v",
lncfg.NSTowerClientDB, err)
d.logger.Error(err)
return nil, nil, err
}
}
// Wrap the watchtower server DB and make sure we clean up.
if cfg.Watchtower.Active {
dbs.TowerServerDB, err = wtdb.OpenTowerDB(
databaseBackends.TowerServerDB,
)
if err != nil {
cleanUp()
err := fmt.Errorf("unable to open %s database: %v",
lncfg.NSTowerServerDB, err)
d.logger.Error(err)
return nil, nil, err
}
}
openTime := time.Since(startOpenTime)
d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
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
}