diff --git a/lncfg/db.go b/lncfg/db.go index 86bbc3ef2..01b0f51cd 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -10,9 +10,11 @@ import ( ) const ( - channelDBName = "channel.db" - macaroonDBName = "macaroons.db" - decayedLogDbName = "sphinxreplay.db" + channelDBName = "channel.db" + macaroonDBName = "macaroons.db" + decayedLogDbName = "sphinxreplay.db" + towerClientDBName = "wtclient.db" + towerServerDBName = "watchtower.db" BoltBackend = "bolt" EtcdBackend = "etcd" @@ -28,6 +30,14 @@ const ( // NSDecayedLogDB is the namespace name that we use for the sphinx // replay a.k.a. decayed log DB. NSDecayedLogDB = "decayedlogdb" + + // NSTowerClientDB is the namespace name that we use for the watchtower + // client DB. + NSTowerClientDB = "towerclientdb" + + // NSTowerServerDB is the namespace name that we use for the watchtower + // server DB. + NSTowerServerDB = "towerserverdb" ) // DB holds database configuration for LND. @@ -117,6 +127,14 @@ type DatabaseBackends struct { // data. DecayedLogDB kvdb.Backend + // TowerClientDB points to a database backend that stores the watchtower + // client data. This might be nil if the watchtower client is disabled. + TowerClientDB kvdb.Backend + + // TowerServerDB points to a database backend that stores the watchtower + // server data. This might be nil if the watchtower server is disabled. + TowerServerDB kvdb.Backend + // Remote indicates whether the database backends are remote, possibly // replicated instances or local bbolt backed databases. Remote bool @@ -128,7 +146,8 @@ type DatabaseBackends struct { // GetBackends returns a set of kvdb.Backends as set in the DB config. func (db *DB) GetBackends(ctx context.Context, chanDBPath, - walletDBPath string) (*DatabaseBackends, error) { + walletDBPath, towerServerDBPath string, towerClientEnabled, + towerServerEnabled bool) (*DatabaseBackends, error) { // 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 @@ -159,13 +178,15 @@ func (db *DB) GetBackends(ctx context.Context, chanDBPath, returnEarly = false return &DatabaseBackends{ - GraphDB: etcdBackend, - ChanStateDB: etcdBackend, - HeightHintDB: etcdBackend, - MacaroonDB: etcdBackend, - DecayedLogDB: etcdBackend, - Remote: true, - CloseFuncs: closeFuncs, + GraphDB: etcdBackend, + ChanStateDB: etcdBackend, + HeightHintDB: etcdBackend, + MacaroonDB: etcdBackend, + DecayedLogDB: etcdBackend, + TowerClientDB: etcdBackend, + TowerServerDB: etcdBackend, + Remote: true, + CloseFuncs: closeFuncs, }, nil } @@ -209,14 +230,58 @@ func (db *DB) GetBackends(ctx context.Context, chanDBPath, } closeFuncs[NSDecayedLogDB] = decayedLogBackend.Close + // The tower client is optional and might not be enabled by the user. We + // handle it being nil properly in the main server. + var towerClientBackend kvdb.Backend + if towerClientEnabled { + towerClientBackend, err = kvdb.GetBoltBackend( + &kvdb.BoltBackendConfig{ + DBPath: chanDBPath, + DBFileName: towerClientDBName, + 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 tower client "+ + "DB: %v", err) + } + closeFuncs[NSTowerClientDB] = towerClientBackend.Close + } + + // The tower server is optional and might not be enabled by the user. We + // handle it being nil properly in the main server. + var towerServerBackend kvdb.Backend + if towerServerEnabled { + towerServerBackend, err = kvdb.GetBoltBackend( + &kvdb.BoltBackendConfig{ + DBPath: towerServerDBPath, + DBFileName: towerServerDBName, + 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 tower server "+ + "DB: %v", err) + } + closeFuncs[NSTowerServerDB] = towerServerBackend.Close + } + returnEarly = false return &DatabaseBackends{ - GraphDB: boltBackend, - ChanStateDB: boltBackend, - HeightHintDB: boltBackend, - MacaroonDB: macaroonBackend, - DecayedLogDB: decayedLogBackend, - CloseFuncs: closeFuncs, + GraphDB: boltBackend, + ChanStateDB: boltBackend, + HeightHintDB: boltBackend, + MacaroonDB: macaroonBackend, + DecayedLogDB: decayedLogBackend, + TowerClientDB: towerClientBackend, + TowerServerDB: towerServerBackend, + CloseFuncs: closeFuncs, }, nil } diff --git a/lnd.go b/lnd.go index ebbfec2c4..348473df2 100644 --- a/lnd.go +++ b/lnd.go @@ -55,6 +55,7 @@ import ( "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" ) @@ -769,23 +770,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error "is proxying over Tor as well", cfg.Tor.StreamIsolation) } - // If the watchtower client should be active, open the client database. - // This is done here so that Close always executes when lndMain returns. - var towerClientDB *wtdb.ClientDB - if cfg.WtClient.Active { - var err error - towerClientDB, err = wtdb.OpenClientDB( - cfg.graphDatabaseDir(), cfg.DB.Bolt.DBTimeout, - ) - if err != nil { - err := fmt.Errorf("unable to open watchtower client "+ - "database: %v", err) - ltndLog.Error(err) - return err - } - defer towerClientDB.Close() - } - // If tor is active and either v2 or v3 onion services have been specified, // make a tor controller and pass it into both the watchtower server and // the regular lnd server. @@ -810,24 +794,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error var tower *watchtower.Standalone if cfg.Watchtower.Active { - // Segment the watchtower directory by chain and network. - towerDBDir := filepath.Join( - cfg.Watchtower.TowerDir, - cfg.registeredChains.PrimaryChain().String(), - lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name), - ) - - towerDB, err := wtdb.OpenTowerDB( - towerDBDir, cfg.DB.Bolt.DBTimeout, - ) - if err != nil { - err := fmt.Errorf("unable to open watchtower "+ - "database: %v", err) - ltndLog.Error(err) - return err - } - defer towerDB.Close() - towerKeyDesc, err := activeChainControl.KeyRing.DeriveKey( keychain.KeyLocator{ Family: keychain.KeyFamilyTowerID, @@ -842,7 +808,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error wtCfg := &watchtower.Config{ BlockFetcher: activeChainControl.ChainIO, - DB: towerDB, + DB: dbs.towerServerDB, EpochRegistrar: activeChainControl.ChainNotifier, Net: cfg.net, NewAddress: func() (btcutil.Address, error) { @@ -894,9 +860,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error // Set up the core server which will listen for incoming peer // connections. server, err := newServer( - cfg, cfg.Listeners, dbs, towerClientDB, - activeChainControl, &idKeyDesc, walletInitParams.ChansToRestore, - chainedAcceptor, torController, + cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, + walletInitParams.ChansToRestore, chainedAcceptor, torController, ) if err != nil { err := fmt.Errorf("unable to create server: %v", err) @@ -1612,11 +1577,13 @@ func waitForWalletPassword(cfg *Config, // databaseInstances is a struct that holds all instances to the actual // databases that are used in lnd. type databaseInstances struct { - graphDB *channeldb.DB - chanStateDB *channeldb.DB - heightHintDB kvdb.Backend - macaroonDB kvdb.Backend - decayedLogDB kvdb.Backend + graphDB *channeldb.DB + chanStateDB *channeldb.DB + heightHintDB kvdb.Backend + macaroonDB kvdb.Backend + decayedLogDB kvdb.Backend + towerClientDB wtclient.DB + towerServerDB watchtower.DB } // initializeDatabases extracts the current databases that we'll use for normal @@ -1637,7 +1604,11 @@ func initializeDatabases(ctx context.Context, startOpenTime := time.Now() databaseBackends, err := cfg.DB.GetBackends( - ctx, cfg.graphDatabaseDir(), cfg.networkDir, + 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 "+ @@ -1709,6 +1680,36 @@ func initializeDatabases(ctx context.Context, // 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) + ltndLog.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) + ltndLog.Error(err) + return nil, nil, err + } + } + openTime := time.Since(startOpenTime) ltndLog.Infof("Database(s) now open (time_to_open=%v)!", openTime) diff --git a/server.go b/server.go index 35293b2d0..b21c71a47 100644 --- a/server.go +++ b/server.go @@ -352,8 +352,7 @@ func noiseDial(idKey keychain.SingleKeyECDH, // newServer creates a new instance of the server which is to listen using the // passed listener address. func newServer(cfg *Config, listenAddrs []net.Addr, - dbs *databaseInstances, - towerClientDB wtclient.DB, cc *chainreg.ChainControl, + dbs *databaseInstances, cc *chainreg.ChainControl, nodeKeyDesc *keychain.KeyDescriptor, chansToRestore walletunlocker.ChannelsToRecover, chanPredicate chanacceptor.ChannelAcceptor, @@ -1306,7 +1305,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, SecretKeyRing: s.cc.KeyRing, Dial: cfg.net.Dial, AuthDial: authDial, - DB: towerClientDB, + DB: dbs.towerClientDB, Policy: policy, ChainHash: *s.cfg.ActiveNetParams.GenesisHash, MinBackoff: 10 * time.Second, @@ -1329,7 +1328,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, SecretKeyRing: s.cc.KeyRing, Dial: cfg.net.Dial, AuthDial: authDial, - DB: towerClientDB, + DB: dbs.towerClientDB, Policy: anchorPolicy, ChainHash: *s.cfg.ActiveNetParams.GenesisHash, MinBackoff: 10 * time.Second, diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 7f98a6985..101c792e9 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "net" - "time" "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/kvdb" @@ -14,11 +13,6 @@ import ( "github.com/lightningnetwork/lnd/watchtower/blob" ) -const ( - // clientDBName is the filename of client database. - clientDBName = "wtclient.db" -) - var ( // cSessionKeyIndexBkt is a top-level bucket storing: // tower-id -> reserved-session-key-index (uint32). @@ -116,11 +110,43 @@ var ( ErrLastTowerAddr = errors.New("cannot remove last tower address") ) +// NewBoltBackendCreator returns a function that creates a new bbolt backend for +// the watchtower database. +func NewBoltBackendCreator(active bool, dbPath, + dbFileName string) func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) { + + // If the watchtower client isn't active, we return a function that + // always returns a nil DB to make sure we don't create empty database + // files. + if !active { + return func(_ *kvdb.BoltConfig) (kvdb.Backend, error) { + return nil, nil + } + } + + return func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) { + cfg := &kvdb.BoltBackendConfig{ + DBPath: dbPath, + DBFileName: dbFileName, + NoFreelistSync: !boltCfg.SyncFreelist, + AutoCompact: boltCfg.AutoCompact, + AutoCompactMinAge: boltCfg.AutoCompactMinAge, + DBTimeout: boltCfg.DBTimeout, + } + + db, err := kvdb.GetBoltBackend(cfg) + if err != nil { + return nil, fmt.Errorf("could not open boltdb: %v", err) + } + + return db, nil + } +} + // ClientDB is single database providing a persistent storage engine for the // wtclient. type ClientDB struct { - db kvdb.Backend - dbPath string + db kvdb.Backend } // OpenClientDB opens the client database given the path to the database's @@ -130,22 +156,19 @@ type ClientDB struct { // migrations will be applied before returning. Any attempt to open a database // with a version number higher that the latest version will fail to prevent // accidental reversion. -func OpenClientDB(dbPath string, dbTimeout time.Duration) (*ClientDB, error) { - bdb, firstInit, err := createDBIfNotExist( - dbPath, clientDBName, dbTimeout, - ) +func OpenClientDB(db kvdb.Backend) (*ClientDB, error) { + firstInit, err := isFirstInit(db) if err != nil { return nil, err } clientDB := &ClientDB{ - db: bdb, - dbPath: dbPath, + db: db, } err = initOrSyncVersions(clientDB, firstInit, clientDBVersions) if err != nil { - bdb.Close() + db.Close() return nil, err } @@ -156,7 +179,7 @@ func OpenClientDB(dbPath string, dbTimeout time.Duration) (*ClientDB, error) { // missing, this will trigger a ErrUninitializedDB error. err = kvdb.Update(clientDB.db, initClientDBBuckets, func() {}) if err != nil { - bdb.Close() + db.Close() return nil, err } diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 448ae45d2..0f9235c4d 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -771,6 +771,7 @@ func checkAckedUpdates(t *testing.T, session *wtdb.ClientSession, // and the mock implementation. This ensures that all databases function // identically, especially in the negative paths. func TestClientDB(t *testing.T) { + dbCfg := &kvdb.BoltConfig{DBTimeout: kvdb.DefaultDBTimeout} dbs := []struct { name string init clientDBInit @@ -784,9 +785,15 @@ func TestClientDB(t *testing.T) { err) } - db, err := wtdb.OpenClientDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err := wtdb.NewBoltBackendCreator( + true, path, "wtclient.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err := wtdb.OpenClientDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to open db: %v", err) @@ -809,18 +816,30 @@ func TestClientDB(t *testing.T) { err) } - db, err := wtdb.OpenClientDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err := wtdb.NewBoltBackendCreator( + true, path, "wtclient.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err := wtdb.OpenClientDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to open db: %v", err) } db.Close() - db, err = wtdb.OpenClientDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err = wtdb.NewBoltBackendCreator( + true, path, "wtclient.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err = wtdb.OpenClientDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to reopen db: %v", err) diff --git a/watchtower/wtdb/db_common.go b/watchtower/wtdb/db_common.go index de14bff64..68df96305 100644 --- a/watchtower/wtdb/db_common.go +++ b/watchtower/wtdb/db_common.go @@ -3,18 +3,10 @@ package wtdb import ( "encoding/binary" "errors" - "os" - "path/filepath" - "time" "github.com/lightningnetwork/lnd/kvdb" ) -const ( - // dbFilePermission requests read+write access to the db file. - dbFilePermission = 0600 -) - var ( // metadataBkt stores all the meta information concerning the state of // the database. @@ -35,67 +27,19 @@ var ( byteOrder = binary.BigEndian ) -// fileExists returns true if the file exists, and false otherwise. -func fileExists(path string) bool { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return false - } - } - - return true -} - -// createDBIfNotExist opens the boltdb database at dbPath/name, creating one if -// one doesn't exist. The boolean returned indicates if the database did not -// exist before, or if it has been created but no version metadata exists within -// it. -func createDBIfNotExist(dbPath, name string, - dbTimeout time.Duration) (kvdb.Backend, bool, error) { - - path := filepath.Join(dbPath, name) - - // If the database file doesn't exist, this indicates we much initialize - // a fresh database with the latest version. - firstInit := !fileExists(path) - if firstInit { - // Ensure all parent directories are initialized. - err := os.MkdirAll(dbPath, 0700) - if err != nil { - return nil, false, err - } - } - - // Specify bbolt freelist options to reduce heap pressure in case the - // freelist grows to be very large. - bdb, err := kvdb.Create( - kvdb.BoltBackendName, path, true, dbTimeout, - ) +// isFirstInit returns true if the given database has not yet been initialized, +// e.g. no metadata bucket is present yet. +func isFirstInit(db kvdb.Backend) (bool, error) { + var metadataExists bool + err := kvdb.View(db, func(tx kvdb.RTx) error { + metadataExists = tx.ReadBucket(metadataBkt) != nil + return nil + }, func() { + metadataExists = false + }) if err != nil { - return nil, false, err + return false, err } - // If the file existed previously, we'll now check to see that the - // metadata bucket is properly initialized. It could be the case that - // the database was created, but we failed to actually populate any - // metadata. If the metadata bucket does not actually exist, we'll - // set firstInit to true so that we can treat is initialize the bucket. - if !firstInit { - var metadataExists bool - err = kvdb.View(bdb, func(tx kvdb.RTx) error { - metadataExists = tx.ReadBucket(metadataBkt) != nil - return nil - }, func() { - metadataExists = false - }) - if err != nil { - return nil, false, err - } - - if !metadataExists { - firstInit = true - } - } - - return bdb, firstInit, nil + return !metadataExists, nil } diff --git a/watchtower/wtdb/tower_db.go b/watchtower/wtdb/tower_db.go index 6f67298f8..50d491411 100644 --- a/watchtower/wtdb/tower_db.go +++ b/watchtower/wtdb/tower_db.go @@ -3,7 +3,6 @@ package wtdb import ( "bytes" "errors" - "time" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" @@ -11,11 +10,6 @@ import ( "github.com/lightningnetwork/lnd/watchtower/blob" ) -const ( - // towerDBName is the filename of tower database. - towerDBName = "watchtower.db" -) - var ( // sessionsBkt is a bucket containing all negotiated client sessions. // session id -> session @@ -56,8 +50,7 @@ var ( // TowerDB is single database providing a persistent storage engine for the // wtserver and lookout subsystems. type TowerDB struct { - db kvdb.Backend - dbPath string + db kvdb.Backend } // OpenTowerDB opens the tower database given the path to the database's @@ -67,22 +60,19 @@ type TowerDB struct { // migrations will be applied before returning. Any attempt to open a database // with a version number higher that the latest version will fail to prevent // accidental reversion. -func OpenTowerDB(dbPath string, dbTimeout time.Duration) (*TowerDB, error) { - bdb, firstInit, err := createDBIfNotExist( - dbPath, towerDBName, dbTimeout, - ) +func OpenTowerDB(db kvdb.Backend) (*TowerDB, error) { + firstInit, err := isFirstInit(db) if err != nil { return nil, err } towerDB := &TowerDB{ - db: bdb, - dbPath: dbPath, + db: db, } err = initOrSyncVersions(towerDB, firstInit, towerDBVersions) if err != nil { - bdb.Close() + db.Close() return nil, err } @@ -93,7 +83,7 @@ func OpenTowerDB(dbPath string, dbTimeout time.Duration) (*TowerDB, error) { // missing, this will trigger a ErrUninitializedDB error. err = kvdb.Update(towerDB.db, initTowerDBBuckets, func() {}) if err != nil { - bdb.Close() + db.Close() return nil, err } diff --git a/watchtower/wtdb/tower_db_test.go b/watchtower/wtdb/tower_db_test.go index 7debe8a0e..413f7c43b 100644 --- a/watchtower/wtdb/tower_db_test.go +++ b/watchtower/wtdb/tower_db_test.go @@ -630,6 +630,7 @@ var stateUpdateInvalidBlobSize = stateUpdateTest{ } func TestTowerDB(t *testing.T) { + dbCfg := &kvdb.BoltConfig{DBTimeout: kvdb.DefaultDBTimeout} dbs := []struct { name string init dbInit @@ -643,9 +644,15 @@ func TestTowerDB(t *testing.T) { err) } - db, err := wtdb.OpenTowerDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err := wtdb.NewBoltBackendCreator( + true, path, "watchtower.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err := wtdb.OpenTowerDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to open db: %v", err) @@ -668,9 +675,15 @@ func TestTowerDB(t *testing.T) { err) } - db, err := wtdb.OpenTowerDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err := wtdb.NewBoltBackendCreator( + true, path, "watchtower.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err := wtdb.OpenTowerDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to open db: %v", err) @@ -680,9 +693,15 @@ func TestTowerDB(t *testing.T) { // Open the db again, ensuring we test a // different path during open and that all // buckets remain initialized. - db, err = wtdb.OpenTowerDB( - path, kvdb.DefaultDBTimeout, - ) + bdb, err = wtdb.NewBoltBackendCreator( + true, path, "watchtower.db", + )(dbCfg) + if err != nil { + os.RemoveAll(path) + t.Fatalf("unable to open db: %v", err) + } + + db, err = wtdb.OpenTowerDB(bdb) if err != nil { os.RemoveAll(path) t.Fatalf("unable to open db: %v", err)