lnd.xprv/watchtower/wtdb/version.go
Conner Fromknecht ec7c16fdc1
watchtower/wtdb: prepare for addition of client db
This commit renames the variables dbName to towerDBName and dbVersions
to towerDBVersions, to distinguish between the upcoming clientDBName
clientDBVersions. We also move resusable portions of the database
initialization and default endianness to its own file so that it can be
shared between both tower and client databases.
2019-05-23 20:47:08 -07:00

159 lines
4.8 KiB
Go

package wtdb
import (
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb"
)
// migration is a function which takes a prior outdated version of the database
// instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database.
type migration func(tx *bbolt.Tx) error
// version pairs a version number with the migration that would need to be
// applied from the prior version to upgrade.
type version struct {
migration migration
}
// towerDBVersions stores all versions and migrations of the tower database.
// This list will be used when opening the database to determine if any
// migrations must be applied.
var towerDBVersions = []version{}
// getLatestDBVersion returns the last known database version.
func getLatestDBVersion(versions []version) uint32 {
return uint32(len(versions))
}
// getMigrations returns a slice of all updates with a greater number that
// curVersion that need to be applied to sync up with the latest version.
func getMigrations(versions []version, curVersion uint32) []version {
var updates []version
for i, v := range versions {
if uint32(i)+1 > curVersion {
updates = append(updates, v)
}
}
return updates
}
// getDBVersion retrieves the current database version from the metadata bucket
// using the dbVersionKey.
func getDBVersion(tx *bbolt.Tx) (uint32, error) {
metadata := tx.Bucket(metadataBkt)
if metadata == nil {
return 0, ErrUninitializedDB
}
versionBytes := metadata.Get(dbVersionKey)
if len(versionBytes) != 4 {
return 0, ErrNoDBVersion
}
return byteOrder.Uint32(versionBytes), nil
}
// initDBVersion initializes the top-level metadata bucket and writes the passed
// version number as the current version.
func initDBVersion(tx *bbolt.Tx, version uint32) error {
_, err := tx.CreateBucketIfNotExists(metadataBkt)
if err != nil {
return err
}
return putDBVersion(tx, version)
}
// putDBVersion stores the passed database version in the metadata bucket under
// the dbVersionKey.
func putDBVersion(tx *bbolt.Tx, version uint32) error {
metadata := tx.Bucket(metadataBkt)
if metadata == nil {
return ErrUninitializedDB
}
versionBytes := make([]byte, 4)
byteOrder.PutUint32(versionBytes, version)
return metadata.Put(dbVersionKey, versionBytes)
}
// versionedDB is a private interface implemented by both the tower and client
// databases, permitting all versioning operations to be performed generically
// on either.
type versionedDB interface {
// bdb returns the underlying bbolt database.
bdb() *bbolt.DB
// Version returns the current version stored in the database.
Version() (uint32, error)
}
// initOrSyncVersions ensures that the database version is properly set before
// opening the database up for regular use. When the database is being
// initialized for the first time, the caller should set init to true, which
// will simply write the latest version to the database. Otherwise, passing init
// as false will cause the database to apply any needed migrations to ensure its
// version matches the latest version in the provided versions list.
func initOrSyncVersions(db versionedDB, init bool, versions []version) error {
// If the database has not yet been created, we'll initialize the
// database version with the latest known version.
if init {
return db.bdb().Update(func(tx *bbolt.Tx) error {
return initDBVersion(tx, getLatestDBVersion(versions))
})
}
// Otherwise, ensure that any migrations are applied to ensure the data
// is in the format expected by the latest version.
return syncVersions(db, versions)
}
// syncVersions ensures the database version is consistent with the highest
// known database version, applying any migrations that have not been made. If
// the highest known version number is lower than the database's version, this
// method will fail to prevent accidental reversions.
func syncVersions(db versionedDB, versions []version) error {
curVersion, err := db.Version()
if err != nil {
return err
}
latestVersion := getLatestDBVersion(versions)
switch {
// Current version is higher than any known version, fail to prevent
// reversion.
case curVersion > latestVersion:
return channeldb.ErrDBReversion
// Current version matches highest known version, nothing to do.
case curVersion == latestVersion:
return nil
}
// Otherwise, apply any migrations in order to bring the database
// version up to the highest known version.
updates := getMigrations(versions, curVersion)
return db.bdb().Update(func(tx *bbolt.Tx) error {
for i, update := range updates {
if update.migration == nil {
continue
}
version := curVersion + uint32(i) + 1
log.Infof("Applying migration #%d", version)
err := update.migration(tx)
if err != nil {
log.Errorf("Unable to apply migration #%d: %v",
version, err)
return err
}
}
return putDBVersion(tx, latestVersion)
})
}