multi: make macaroon DB remote compatible

The macaroon root keys should also be stored to the remote database if a
replicated backend such as etcd is used.
This commit refactors the macaroons service and wallet unlocker to
accept a kvdb backend directly instead of creating the bolt instance
automatically.
This commit is contained in:
Oliver Gugger 2021-08-03 09:57:30 +02:00
parent 0d3647d715
commit f7b17df452
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
8 changed files with 121 additions and 104 deletions

View File

@ -10,7 +10,8 @@ import (
) )
const ( const (
dbName = "channel.db" channelDBName = "channel.db"
macaroonDBName = "macaroons.db"
BoltBackend = "bolt" BoltBackend = "bolt"
EtcdBackend = "etcd" EtcdBackend = "etcd"
@ -19,6 +20,9 @@ const (
// NSChannelDB is the namespace name that we use for the combined graph // NSChannelDB is the namespace name that we use for the combined graph
// and channel state DB. // and channel state DB.
NSChannelDB = "channeldb" NSChannelDB = "channeldb"
// NSMacaroonDB is the namespace name that we use for the macaroon DB.
NSMacaroonDB = "macaroondb"
) )
// DB holds database configuration for LND. // DB holds database configuration for LND.
@ -100,6 +104,10 @@ type DatabaseBackends struct {
// contains the chain height hint related data. // contains the chain height hint related data.
HeightHintDB kvdb.Backend HeightHintDB kvdb.Backend
// MacaroonDB points to a database backend that stores the macaroon root
// keys.
MacaroonDB kvdb.Backend
// Remote indicates whether the database backends are remote, possibly // Remote indicates whether the database backends are remote, possibly
// replicated instances or local bbolt backed databases. // replicated instances or local bbolt backed databases.
Remote bool Remote bool
@ -110,14 +118,27 @@ type DatabaseBackends struct {
} }
// GetBackends returns a set of kvdb.Backends as set in the DB config. // GetBackends returns a set of kvdb.Backends as set in the DB config.
func (db *DB) GetBackends(ctx context.Context, dbPath string) ( func (db *DB) GetBackends(ctx context.Context, chanDBPath,
*DatabaseBackends, error) { walletDBPath string) (*DatabaseBackends, error) {
// We keep track of all the kvdb backends we actually open and return a // We keep track of all the kvdb backends we actually open and return a
// reference to their close function so they can be cleaned up properly // reference to their close function so they can be cleaned up properly
// on error or shutdown. // on error or shutdown.
closeFuncs := make(map[string]func() error) closeFuncs := make(map[string]func() error)
// If we need to return early because of an error, we invoke any close
// function that has been initialized so far.
returnEarly := true
defer func() {
if !returnEarly {
return
}
for _, closeFunc := range closeFuncs {
_ = closeFunc()
}
}()
if db.Backend == EtcdBackend { if db.Backend == EtcdBackend {
etcdBackend, err := kvdb.Open( etcdBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx, db.Etcd, kvdb.EtcdBackendName, ctx, db.Etcd,
@ -127,10 +148,12 @@ func (db *DB) GetBackends(ctx context.Context, dbPath string) (
} }
closeFuncs[NSChannelDB] = etcdBackend.Close closeFuncs[NSChannelDB] = etcdBackend.Close
returnEarly = false
return &DatabaseBackends{ return &DatabaseBackends{
GraphDB: etcdBackend, GraphDB: etcdBackend,
ChanStateDB: etcdBackend, ChanStateDB: etcdBackend,
HeightHintDB: etcdBackend, HeightHintDB: etcdBackend,
MacaroonDB: etcdBackend,
Remote: true, Remote: true,
CloseFuncs: closeFuncs, CloseFuncs: closeFuncs,
}, nil }, nil
@ -138,8 +161,8 @@ func (db *DB) GetBackends(ctx context.Context, dbPath string) (
// We're using all bbolt based databases by default. // We're using all bbolt based databases by default.
boltBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{ boltBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: dbPath, DBPath: chanDBPath,
DBFileName: dbName, DBFileName: channelDBName,
DBTimeout: db.Bolt.DBTimeout, DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: !db.Bolt.SyncFreelist, NoFreelistSync: !db.Bolt.SyncFreelist,
AutoCompact: db.Bolt.AutoCompact, AutoCompact: db.Bolt.AutoCompact,
@ -150,10 +173,25 @@ func (db *DB) GetBackends(ctx context.Context, dbPath string) (
} }
closeFuncs[NSChannelDB] = boltBackend.Close closeFuncs[NSChannelDB] = boltBackend.Close
macaroonBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: walletDBPath,
DBFileName: macaroonDBName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: !db.Bolt.SyncFreelist,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
})
if err != nil {
return nil, fmt.Errorf("error opening macaroon DB: %v", err)
}
closeFuncs[NSMacaroonDB] = macaroonBackend.Close
returnEarly = false
return &DatabaseBackends{ return &DatabaseBackends{
GraphDB: boltBackend, GraphDB: boltBackend,
ChanStateDB: boltBackend, ChanStateDB: boltBackend,
HeightHintDB: boltBackend, HeightHintDB: boltBackend,
MacaroonDB: macaroonBackend,
CloseFuncs: closeFuncs, CloseFuncs: closeFuncs,
}, nil }, nil
} }

19
lnd.go
View File

@ -480,6 +480,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
} }
pwService.SetLoaderOpts([]btcwallet.LoaderOption{loaderOpt}) pwService.SetLoaderOpts([]btcwallet.LoaderOption{loaderOpt})
pwService.SetMacaroonDB(dbs.macaroonDB)
walletExists, err := pwService.WalletExists() walletExists, err := pwService.WalletExists()
if err != nil { if err != nil {
return err return err
@ -584,8 +585,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
if !cfg.NoMacaroons { if !cfg.NoMacaroons {
// Create the macaroon authentication/authorization service. // Create the macaroon authentication/authorization service.
macaroonService, err = macaroons.NewService( macaroonService, err = macaroons.NewService(
cfg.networkDir, "lnd", walletInitParams.StatelessInit, dbs.macaroonDB, "lnd", walletInitParams.StatelessInit,
cfg.DB.Bolt.DBTimeout, macaroons.IPLockChecker, macaroons.IPLockChecker,
) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to set up macaroon "+ err := fmt.Errorf("unable to set up macaroon "+
@ -1326,11 +1327,6 @@ type WalletUnlockParams struct {
// createWalletUnlockerService creates a WalletUnlockerService from the passed // createWalletUnlockerService creates a WalletUnlockerService from the passed
// config. // config.
func createWalletUnlockerService(cfg *Config) *walletunlocker.UnlockerService { func createWalletUnlockerService(cfg *Config) *walletunlocker.UnlockerService {
chainConfig := cfg.Bitcoin
if cfg.registeredChains.PrimaryChain() == chainreg.LitecoinChain {
chainConfig = cfg.Litecoin
}
// The macaroonFiles are passed to the wallet unlocker so they can be // The macaroonFiles are passed to the wallet unlocker so they can be
// deleted and recreated in case the root macaroon key is also changed // deleted and recreated in case the root macaroon key is also changed
// during the change password operation. // during the change password operation.
@ -1339,8 +1335,7 @@ func createWalletUnlockerService(cfg *Config) *walletunlocker.UnlockerService {
} }
return walletunlocker.New( return walletunlocker.New(
chainConfig.ChainDir, cfg.ActiveNetParams.Params, cfg.ActiveNetParams.Params, macaroonFiles,
!cfg.SyncFreelist, macaroonFiles, cfg.DB.Bolt.DBTimeout,
cfg.ResetWalletTransactions, nil, cfg.ResetWalletTransactions, nil,
) )
} }
@ -1620,6 +1615,7 @@ type databaseInstances struct {
graphDB *channeldb.DB graphDB *channeldb.DB
chanStateDB *channeldb.DB chanStateDB *channeldb.DB
heightHintDB kvdb.Backend heightHintDB kvdb.Backend
macaroonDB kvdb.Backend
} }
// initializeDatabases extracts the current databases that we'll use for normal // initializeDatabases extracts the current databases that we'll use for normal
@ -1639,7 +1635,9 @@ func initializeDatabases(ctx context.Context,
startOpenTime := time.Now() startOpenTime := time.Now()
databaseBackends, err := cfg.DB.GetBackends(ctx, cfg.graphDatabaseDir()) databaseBackends, err := cfg.DB.GetBackends(
ctx, cfg.graphDatabaseDir(), cfg.networkDir,
)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to obtain database "+ return nil, nil, fmt.Errorf("unable to obtain database "+
"backends: %v", err) "backends: %v", err)
@ -1650,6 +1648,7 @@ func initializeDatabases(ctx context.Context,
// within that DB. // within that DB.
dbs := &databaseInstances{ dbs := &databaseInstances{
heightHintDB: databaseBackends.HeightHintDB, heightHintDB: databaseBackends.HeightHintDB,
macaroonDB: databaseBackends.MacaroonDB,
} }
cleanUp := func() { cleanUp := func() {
// We can just close the returned close functions directly. Even // We can just close the returned close functions directly. Even

View File

@ -4,9 +4,6 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os"
"path"
"time"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@ -17,10 +14,6 @@ import (
) )
var ( var (
// DBFilename is the filename within the data directory which contains
// the macaroon stores.
DBFilename = "macaroons.db"
// ErrMissingRootKeyID specifies the root key ID is missing. // ErrMissingRootKeyID specifies the root key ID is missing.
ErrMissingRootKeyID = fmt.Errorf("missing root key ID") ErrMissingRootKeyID = fmt.Errorf("missing root key ID")
@ -68,34 +61,17 @@ type Service struct {
StatelessInit bool StatelessInit bool
} }
// NewService returns a service backed by the macaroon Bolt DB stored in the // NewService returns a service backed by the macaroon DB backend. The `checks`
// passed directory. The `checks` argument can be any of the `Checker` type // argument can be any of the `Checker` type functions defined in this package,
// functions defined in this package, or a custom checker if desired. This // or a custom checker if desired. This constructor prevents double-registration
// constructor prevents double-registration of checkers to prevent panics, so // of checkers to prevent panics, so listing the same checker more than once is
// listing the same checker more than once is not harmful. Default checkers, // not harmful. Default checkers, such as those for `allow`, `time-before`,
// such as those for `allow`, `time-before`, `declared`, and `error` caveats // `declared`, and `error` caveats are registered automatically and don't need
// are registered automatically and don't need to be added. // to be added.
func NewService(dir, location string, statelessInit bool, func NewService(db kvdb.Backend, location string, statelessInit bool,
dbTimeout time.Duration, checks ...Checker) (*Service, error) { checks ...Checker) (*Service, error) {
// Ensure that the path to the directory exists. rootKeyStore, err := NewRootKeyStorage(db)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0700); err != nil {
return nil, err
}
}
// Open the database that we'll use to store the primary macaroon key,
// and all generated macaroons+caveats.
macaroonDB, err := kvdb.Create(
kvdb.BoltBackendName, path.Join(dir, DBFilename), true,
dbTimeout,
)
if err != nil {
return nil, err
}
rootKeyStore, err := NewRootKeyStorage(macaroonDB)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -33,7 +33,7 @@ var (
// default password of 'hello'. Only the path to the temporary // default password of 'hello'. Only the path to the temporary
// DB file is returned, because the service will open the file // DB file is returned, because the service will open the file
// and read the store on its own. // and read the store on its own.
func setupTestRootKeyStorage(t *testing.T) string { func setupTestRootKeyStorage(t *testing.T) (string, kvdb.Backend) {
tempDir, err := ioutil.TempDir("", "macaroonstore-") tempDir, err := ioutil.TempDir("", "macaroonstore-")
if err != nil { if err != nil {
t.Fatalf("Error creating temp dir: %v", err) t.Fatalf("Error creating temp dir: %v", err)
@ -55,21 +55,20 @@ func setupTestRootKeyStorage(t *testing.T) string {
if err != nil { if err != nil {
t.Fatalf("error creating unlock: %v", err) t.Fatalf("error creating unlock: %v", err)
} }
return tempDir return tempDir, db
} }
// TestNewService tests the creation of the macaroon service. // TestNewService tests the creation of the macaroon service.
func TestNewService(t *testing.T) { func TestNewService(t *testing.T) {
// First, initialize a dummy DB file with a store that the service // First, initialize a dummy DB file with a store that the service
// can read from. Make sure the file is removed in the end. // can read from. Make sure the file is removed in the end.
tempDir := setupTestRootKeyStorage(t) tempDir, db := setupTestRootKeyStorage(t)
defer os.RemoveAll(tempDir) defer os.RemoveAll(tempDir)
// Second, create the new service instance, unlock it and pass in a // Second, create the new service instance, unlock it and pass in a
// checker that we expect it to add to the bakery. // checker that we expect it to add to the bakery.
service, err := macaroons.NewService( service, err := macaroons.NewService(
tempDir, "lnd", false, kvdb.DefaultDBTimeout, db, "lnd", false, macaroons.IPLockChecker,
macaroons.IPLockChecker,
) )
if err != nil { if err != nil {
t.Fatalf("Error creating new service: %v", err) t.Fatalf("Error creating new service: %v", err)
@ -117,11 +116,10 @@ func TestNewService(t *testing.T) {
// incoming context. // incoming context.
func TestValidateMacaroon(t *testing.T) { func TestValidateMacaroon(t *testing.T) {
// First, initialize the service and unlock it. // First, initialize the service and unlock it.
tempDir := setupTestRootKeyStorage(t) tempDir, db := setupTestRootKeyStorage(t)
defer os.RemoveAll(tempDir) defer os.RemoveAll(tempDir)
service, err := macaroons.NewService( service, err := macaroons.NewService(
tempDir, "lnd", false, kvdb.DefaultDBTimeout, db, "lnd", false, macaroons.IPLockChecker,
macaroons.IPLockChecker,
) )
if err != nil { if err != nil {
t.Fatalf("Error creating new service: %v", err) t.Fatalf("Error creating new service: %v", err)
@ -175,14 +173,13 @@ func TestValidateMacaroon(t *testing.T) {
func TestListMacaroonIDs(t *testing.T) { func TestListMacaroonIDs(t *testing.T) {
// First, initialize a dummy DB file with a store that the service // First, initialize a dummy DB file with a store that the service
// can read from. Make sure the file is removed in the end. // can read from. Make sure the file is removed in the end.
tempDir := setupTestRootKeyStorage(t) tempDir, db := setupTestRootKeyStorage(t)
defer os.RemoveAll(tempDir) defer os.RemoveAll(tempDir)
// Second, create the new service instance, unlock it and pass in a // Second, create the new service instance, unlock it and pass in a
// checker that we expect it to add to the bakery. // checker that we expect it to add to the bakery.
service, err := macaroons.NewService( service, err := macaroons.NewService(
tempDir, "lnd", false, kvdb.DefaultDBTimeout, db, "lnd", false, macaroons.IPLockChecker,
macaroons.IPLockChecker,
) )
require.NoError(t, err, "Error creating new service") require.NoError(t, err, "Error creating new service")
defer service.Close() defer service.Close()
@ -208,14 +205,13 @@ func TestDeleteMacaroonID(t *testing.T) {
// First, initialize a dummy DB file with a store that the service // First, initialize a dummy DB file with a store that the service
// can read from. Make sure the file is removed in the end. // can read from. Make sure the file is removed in the end.
tempDir := setupTestRootKeyStorage(t) tempDir, db := setupTestRootKeyStorage(t)
defer os.RemoveAll(tempDir) defer os.RemoveAll(tempDir)
// Second, create the new service instance, unlock it and pass in a // Second, create the new service instance, unlock it and pass in a
// checker that we expect it to add to the bakery. // checker that we expect it to add to the bakery.
service, err := macaroons.NewService( service, err := macaroons.NewService(
tempDir, "lnd", false, kvdb.DefaultDBTimeout, db, "lnd", false, macaroons.IPLockChecker,
macaroons.IPLockChecker,
) )
require.NoError(t, err, "Error creating new service") require.NoError(t, err, "Error creating new service")
defer service.Close() defer service.Close()

View File

@ -66,7 +66,6 @@ type RootKeyStorage struct {
} }
// NewRootKeyStorage creates a RootKeyStorage instance. // NewRootKeyStorage creates a RootKeyStorage instance.
// TODO(aakselrod): Add support for encryption of data with passphrase.
func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error) { func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error) {
// If the store's bucket doesn't exist, create it. // If the store's bucket doesn't exist, create it.
err := kvdb.Update(db, func(tx kvdb.RwTx) error { err := kvdb.Update(db, func(tx kvdb.RwTx) error {
@ -78,7 +77,10 @@ func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error) {
} }
// Return the DB wrapped in a RootKeyStorage object. // Return the DB wrapped in a RootKeyStorage object.
return &RootKeyStorage{Backend: db, encKey: nil}, nil return &RootKeyStorage{
Backend: db,
encKey: nil,
}, nil
} }
// CreateUnlock sets an encryption key if one is not already set, otherwise it // CreateUnlock sets an encryption key if one is not already set, otherwise it
@ -97,7 +99,7 @@ func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
return ErrPasswordRequired return ErrPasswordRequired
} }
return kvdb.Update(r, func(tx kvdb.RwTx) error { return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName) bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
@ -153,7 +155,7 @@ func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
return ErrPasswordRequired return ErrPasswordRequired
} }
return kvdb.Update(r, func(tx kvdb.RwTx) error { return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName) bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
@ -225,7 +227,7 @@ func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
return nil, ErrStoreLocked return nil, ErrStoreLocked
} }
var rootKey []byte var rootKey []byte
err := kvdb.View(r, func(tx kvdb.RTx) error { err := kvdb.View(r.Backend, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(rootKeyBucketName) bucket := tx.ReadBucket(rootKeyBucketName)
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
@ -276,7 +278,7 @@ func (r *RootKeyStorage) RootKey(ctx context.Context) ([]byte, []byte, error) {
return nil, nil, ErrKeyValueForbidden return nil, nil, ErrKeyValueForbidden
} }
err = kvdb.Update(r, func(tx kvdb.RwTx) error { err = kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName) bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
@ -319,7 +321,7 @@ func (r *RootKeyStorage) GenerateNewRootKey() error {
if r.encKey == nil { if r.encKey == nil {
return ErrStoreLocked return ErrStoreLocked
} }
return kvdb.Update(r, func(tx kvdb.RwTx) error { return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName) bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil { if bucket == nil {
return ErrRootKeyBucketNotFound return ErrRootKeyBucketNotFound
@ -341,7 +343,13 @@ func (r *RootKeyStorage) Close() error {
r.encKey.Zero() r.encKey.Zero()
r.encKey = nil r.encKey = nil
} }
return r.Backend.Close()
// Since we're not responsible for _creating_ the connection to our DB
// backend, we also shouldn't close it. This should be handled
// externally as to not interfere with remote DB connections in case we
// need to open/close the store twice as happens in the password change
// case.
return nil
} }
// generateAndStoreNewRootKey creates a new random RootKeyLen-byte root key, // generateAndStoreNewRootKey creates a new random RootKeyLen-byte root key,
@ -377,7 +385,7 @@ func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error) {
// Read all the items in the bucket and append the keys, which are the // Read all the items in the bucket and append the keys, which are the
// root key IDs we want. // root key IDs we want.
err := kvdb.View(r, func(tx kvdb.RTx) error { err := kvdb.View(r.Backend, func(tx kvdb.RTx) error {
// appendRootKey is a function closure that appends root key ID // appendRootKey is a function closure that appends root key ID
// to rootKeySlice. // to rootKeySlice.
@ -426,7 +434,7 @@ func (r *RootKeyStorage) DeleteMacaroonID(
} }
var rootKeyIDDeleted []byte var rootKeyIDDeleted []byte
err := kvdb.Update(r, func(tx kvdb.RwTx) error { err := kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName) bucket := tx.ReadWriteBucket(rootKeyBucketName)
// Check the key can be found. If not, return nil. // Check the key can be found. If not, return nil.

View File

@ -54,6 +54,7 @@ func openTestStore(t *testing.T, tempDir string) (func(),
cleanup := func() { cleanup := func() {
_ = store.Close() _ = store.Close()
_ = db.Close()
} }
return cleanup, store return cleanup, store
@ -108,6 +109,7 @@ func TestStore(t *testing.T) {
require.Equal(t, macaroons.ErrAlreadyUnlocked, err) require.Equal(t, macaroons.ErrAlreadyUnlocked, err)
_ = store.Close() _ = store.Close()
_ = store.Backend.Close()
// Between here and the re-opening of the store, it's possible to get // Between here and the re-opening of the store, it's possible to get
// a double-close, but that's not such a big deal since the tests will // a double-close, but that's not such a big deal since the tests will
@ -206,6 +208,8 @@ func TestStoreChangePassword(t *testing.T) {
// after closing. // after closing.
err = store.Close() err = store.Close()
require.NoError(t, err) require.NoError(t, err)
err = store.Backend.Close()
require.NoError(t, err)
err = store.CreateUnlock(&newPw) err = store.CreateUnlock(&newPw)
require.Error(t, err) require.Error(t, err)

View File

@ -13,6 +13,7 @@ import (
"github.com/lightningnetwork/lnd/aezeed" "github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
@ -124,8 +125,6 @@ type UnlockerService struct {
// the WalletUnlocker service. // the WalletUnlocker service.
MacResponseChan chan []byte MacResponseChan chan []byte
chainDir string
noFreelistSync bool
netParams *chaincfg.Params netParams *chaincfg.Params
// macaroonFiles is the path to the three generated macaroons with // macaroonFiles is the path to the three generated macaroons with
@ -133,21 +132,21 @@ type UnlockerService struct {
// initialization of lnd. // initialization of lnd.
macaroonFiles []string macaroonFiles []string
// dbTimeout specifies the timeout value to use when opening the wallet
// database.
dbTimeout time.Duration
// resetWalletTransactions indicates that the wallet state should be // resetWalletTransactions indicates that the wallet state should be
// reset on unlock to force a full chain rescan. // reset on unlock to force a full chain rescan.
resetWalletTransactions bool resetWalletTransactions bool
// LoaderOpts holds the functional options for the wallet loader. // LoaderOpts holds the functional options for the wallet loader.
loaderOpts []btcwallet.LoaderOption loaderOpts []btcwallet.LoaderOption
// macaroonDB is an instance of a database backend that stores all
// macaroon root keys. This will be nil on initialization and must be
// set using the SetMacaroonDB method as soon as it's available.
macaroonDB kvdb.Backend
} }
// New creates and returns a new UnlockerService. // New creates and returns a new UnlockerService.
func New(chainDir string, params *chaincfg.Params, noFreelistSync bool, func New(params *chaincfg.Params, macaroonFiles []string,
macaroonFiles []string, dbTimeout time.Duration,
resetWalletTransactions bool, resetWalletTransactions bool,
loaderOpts []btcwallet.LoaderOption) *UnlockerService { loaderOpts []btcwallet.LoaderOption) *UnlockerService {
@ -158,11 +157,8 @@ func New(chainDir string, params *chaincfg.Params, noFreelistSync bool,
// Make sure we buffer the channel is buffered so the main lnd // Make sure we buffer the channel is buffered so the main lnd
// goroutine isn't blocking on writing to it. // goroutine isn't blocking on writing to it.
MacResponseChan: make(chan []byte, 1), MacResponseChan: make(chan []byte, 1),
chainDir: chainDir,
netParams: params, netParams: params,
macaroonFiles: macaroonFiles, macaroonFiles: macaroonFiles,
dbTimeout: dbTimeout,
noFreelistSync: noFreelistSync,
resetWalletTransactions: resetWalletTransactions, resetWalletTransactions: resetWalletTransactions,
loaderOpts: loaderOpts, loaderOpts: loaderOpts,
} }
@ -174,6 +170,12 @@ func (u *UnlockerService) SetLoaderOpts(loaderOpts []btcwallet.LoaderOption) {
u.loaderOpts = loaderOpts u.loaderOpts = loaderOpts
} }
// SetMacaroonDB can be used to inject the macaroon database after the unlocker
// service has been hooked to the main RPC server.
func (u *UnlockerService) SetMacaroonDB(macaroonDB kvdb.Backend) {
u.macaroonDB = macaroonDB
}
func (u *UnlockerService) newLoader(recoveryWindow uint32) (*wallet.Loader, func (u *UnlockerService) newLoader(recoveryWindow uint32) (*wallet.Loader,
error) { error) {
@ -607,9 +609,8 @@ func (u *UnlockerService) ChangePassword(ctx context.Context,
// then close it again. // then close it again.
// Attempt to open the macaroon DB, unlock it and then change // Attempt to open the macaroon DB, unlock it and then change
// the passphrase. // the passphrase.
netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
macaroonService, err := macaroons.NewService( macaroonService, err := macaroons.NewService(
netDir, "lnd", in.StatelessInit, u.dbTimeout, u.macaroonDB, "lnd", in.StatelessInit,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -113,7 +113,7 @@ func openOrCreateTestMacStore(tempDir string, pw *[]byte,
return nil, err return nil, err
} }
db, err := kvdb.Create( db, err := kvdb.Create(
kvdb.BoltBackendName, path.Join(netDir, macaroons.DBFilename), kvdb.BoltBackendName, path.Join(netDir, "macaroons.db"),
true, kvdb.DefaultDBTimeout, true, kvdb.DefaultDBTimeout,
) )
if err != nil { if err != nil {
@ -154,8 +154,7 @@ func TestGenSeed(t *testing.T) {
}() }()
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
// Now that the service has been created, we'll ask it to generate a // Now that the service has been created, we'll ask it to generate a
@ -192,8 +191,7 @@ func TestGenSeedGenerateEntropy(t *testing.T) {
_ = os.RemoveAll(testDir) _ = os.RemoveAll(testDir)
}() }()
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
// Now that the service has been created, we'll ask it to generate a // Now that the service has been created, we'll ask it to generate a
@ -229,8 +227,7 @@ func TestGenSeedInvalidEntropy(t *testing.T) {
_ = os.RemoveAll(testDir) _ = os.RemoveAll(testDir)
}() }()
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
// Now that the service has been created, we'll ask it to generate a // Now that the service has been created, we'll ask it to generate a
@ -263,8 +260,7 @@ func TestInitWallet(t *testing.T) {
// Create new UnlockerService. // Create new UnlockerService.
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
// Once we have the unlocker service created, we'll now instantiate a // Once we have the unlocker service created, we'll now instantiate a
@ -352,8 +348,7 @@ func TestCreateWalletInvalidEntropy(t *testing.T) {
// Create new UnlockerService. // Create new UnlockerService.
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
// We'll attempt to init the wallet with an invalid cipher seed and // We'll attempt to init the wallet with an invalid cipher seed and
@ -385,8 +380,7 @@ func TestUnlockWallet(t *testing.T) {
// Create new UnlockerService that'll also drop the wallet's history on // Create new UnlockerService that'll also drop the wallet's history on
// unlock. // unlock.
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, nil, kvdb.DefaultDBTimeout, testNetParams, nil, true, testLoaderOpts(testDir),
true, testLoaderOpts(testDir),
) )
ctx := context.Background() ctx := context.Background()
@ -477,9 +471,9 @@ func TestChangeWalletPasswordNewRootkey(t *testing.T) {
// Create a new UnlockerService with our temp files. // Create a new UnlockerService with our temp files.
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, tempFiles, kvdb.DefaultDBTimeout, testNetParams, tempFiles, false, testLoaderOpts(testDir),
false, testLoaderOpts(testDir),
) )
service.SetMacaroonDB(store.Backend)
ctx := context.Background() ctx := context.Background()
newPassword := []byte("hunter2???") newPassword := []byte("hunter2???")
@ -588,10 +582,11 @@ func TestChangeWalletPasswordStateless(t *testing.T) {
// Create a new UnlockerService with our temp files. // Create a new UnlockerService with our temp files.
service := walletunlocker.New( service := walletunlocker.New(
testDir, testNetParams, true, []string{ testNetParams, []string{
tempMacFile, nonExistingFile, tempMacFile, nonExistingFile,
}, kvdb.DefaultDBTimeout, false, testLoaderOpts(testDir), }, false, testLoaderOpts(testDir),
) )
service.SetMacaroonDB(store.Backend)
// Create a wallet we can try to unlock. We use the default password // Create a wallet we can try to unlock. We use the default password
// so we can check that the unlocker service defaults to this when // so we can check that the unlocker service defaults to this when